/**
* Cesium - https://github.com/AnalyticalGraphicsInc/cesium
*
* Copyright 2011-2016 Cesium Contributors
*
* 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.
*
* Columbus View (Pat. Pend.)
*
* Portions licensed separately.
* See https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md for full licensing details.
*/
(function () {
/**
* @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
* Released under MIT license, http://github.com/requirejs/almond/LICENSE
*/
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*global setTimeout: false */
var requirejs, require, define;
(function (undef) {
var main, req, makeMap, handlers,
defined = {},
waiting = {},
config = {},
defining = {},
hasOwn = Object.prototype.hasOwnProperty,
aps = [].slice,
jsSuffixRegExp = /\.js$/;
function hasProp(obj, prop) {
return hasOwn.call(obj, prop);
}
/**
* Given a relative module name, like ./something, normalize it to
* a real name that can be mapped to a path.
* @param {String} name the relative name
* @param {String} baseName a real name that the name arg is relative
* to.
* @returns {String} normalized name
*/
function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
baseParts = baseName && baseName.split("/"),
map = config.map,
starMap = (map && map['*']) || {};
//Adjust any relative paths.
if (name) {
name = name.split('/');
lastIndex = name.length - 1;
// If wanting node ID compatibility, strip .js from end
// of IDs. Have to do this here, and not in nameToUrl
// because node allows either .js or non .js to map
// to same file.
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
}
// Starts with a '.' so need the baseName
if (name[0].charAt(0) === '.' && baseParts) {
//Convert baseName to array, and lop off the last part,
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
name = normalizedBaseParts.concat(name);
}
//start trimDots
for (i = 0; i < name.length; i++) {
part = name[i];
if (part === '.') {
name.splice(i, 1);
i -= 1;
} else if (part === '..') {
// If at the start, or previous value is still ..,
// keep them so that when converted to a path it may
// still work when converted to a path, even though
// as an ID it is less than ideal. In larger point
// releases, may be better to just kick out an error.
if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
continue;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
}
}
//end trimDots
name = name.join('/');
}
//Apply map config if available.
if ((baseParts || starMap) && map) {
nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) {
nameSegment = nameParts.slice(0, i).join("/");
if (baseParts) {
//Find the longest baseName segment match in the config.
//So, do joins on the biggest to smallest lengths of baseParts.
for (j = baseParts.length; j > 0; j -= 1) {
mapValue = map[baseParts.slice(0, j).join('/')];
//baseName segment has config, find if it has one for
//this name.
if (mapValue) {
mapValue = mapValue[nameSegment];
if (mapValue) {
//Match, update name to the new value.
foundMap = mapValue;
foundI = i;
break;
}
}
}
}
if (foundMap) {
break;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if (!foundStarMap && starMap && starMap[nameSegment]) {
foundStarMap = starMap[nameSegment];
starI = i;
}
}
if (!foundMap && foundStarMap) {
foundMap = foundStarMap;
foundI = starI;
}
if (foundMap) {
nameParts.splice(0, foundI, foundMap);
name = nameParts.join('/');
}
}
return name;
}
function makeRequire(relName, forceSync) {
return function () {
//A version of a require function that passes a moduleName
//value for items that may need to
//look up paths relative to the moduleName
var args = aps.call(arguments, 0);
//If first arg is not require('string'), and there is only
//one arg, it is the array form without a callback. Insert
//a null so that the following concat is correct.
if (typeof args[0] !== 'string' && args.length === 1) {
args.push(null);
}
return req.apply(undef, args.concat([relName, forceSync]));
};
}
function makeNormalize(relName) {
return function (name) {
return normalize(name, relName);
};
}
function makeLoad(depName) {
return function (value) {
defined[depName] = value;
};
}
function callDep(name) {
if (hasProp(waiting, name)) {
var args = waiting[name];
delete waiting[name];
defining[name] = true;
main.apply(undef, args);
}
if (!hasProp(defined, name) && !hasProp(defining, name)) {
throw new Error('No ' + name);
}
return defined[name];
}
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix(name) {
var prefix,
index = name ? name.indexOf('!') : -1;
if (index > -1) {
prefix = name.substring(0, index);
name = name.substring(index + 1, name.length);
}
return [prefix, name];
}
//Creates a parts array for a relName where first part is plugin ID,
//second part is resource ID. Assumes relName has already been normalized.
function makeRelParts(relName) {
return relName ? splitPrefix(relName) : [];
}
/**
* Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin
* too, as an optimization.
*/
makeMap = function (name, relParts) {
var plugin,
parts = splitPrefix(name),
prefix = parts[0],
relResourceName = relParts[1];
name = parts[1];
if (prefix) {
prefix = normalize(prefix, relResourceName);
plugin = callDep(prefix);
}
//Normalize according
if (prefix) {
if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relResourceName));
} else {
name = normalize(name, relResourceName);
}
} else {
name = normalize(name, relResourceName);
parts = splitPrefix(name);
prefix = parts[0];
name = parts[1];
if (prefix) {
plugin = callDep(prefix);
}
}
//Using ridiculous property names for space reasons
return {
f: prefix ? prefix + '!' + name : name, //fullName
n: name,
pr: prefix,
p: plugin
};
};
function makeConfig(name) {
return function () {
return (config && config.config && config.config[name]) || {};
};
}
handlers = {
require: function (name) {
return makeRequire(name);
},
exports: function (name) {
var e = defined[name];
if (typeof e !== 'undefined') {
return e;
} else {
return (defined[name] = {});
}
},
module: function (name) {
return {
id: name,
uri: '',
exports: defined[name],
config: makeConfig(name)
};
}
};
main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i, relParts,
args = [],
callbackType = typeof callback,
usingExports;
//Use name if no relName
relName = relName || name;
relParts = makeRelParts(relName);
//Call the callback to define the module, if necessary.
if (callbackType === 'undefined' || callbackType === 'function') {
//Pull out the defined dependencies and pass the ordered
//values to the callback.
//Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relParts);
depName = map.f;
//Fast path CommonJS standard dependencies.
if (depName === "require") {
args[i] = handlers.require(name);
} else if (depName === "exports") {
//CommonJS module spec 1.1
args[i] = handlers.exports(name);
usingExports = true;
} else if (depName === "module") {
//CommonJS module spec 1.1
cjsModule = args[i] = handlers.module(name);
} else if (hasProp(defined, depName) ||
hasProp(waiting, depName) ||
hasProp(defining, depName)) {
args[i] = callDep(depName);
} else if (map.p) {
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
args[i] = defined[depName];
} else {
throw new Error(name + ' missing ' + depName);
}
}
ret = callback ? callback.apply(defined[name], args) : undefined;
if (name) {
//If setting exports via "module" is in play,
//favor that over return value and exports. After that,
//favor a non-undefined return value over exports use.
if (cjsModule && cjsModule.exports !== undef &&
cjsModule.exports !== defined[name]) {
defined[name] = cjsModule.exports;
} else if (ret !== undef || !usingExports) {
//Use the return value from the function.
defined[name] = ret;
}
}
} else if (name) {
//May just be an object definition for the module. Only
//worry about defining if have a module name.
defined[name] = callback;
}
};
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
if (typeof deps === "string") {
if (handlers[deps]) {
//callback in this case is really relName
return handlers[deps](callback);
}
//Just return the module wanted. In this scenario, the
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
return callDep(makeMap(deps, makeRelParts(callback)).f);
} else if (!deps.splice) {
//deps is a config object, not an array.
config = deps;
if (config.deps) {
req(config.deps, config.callback);
}
if (!callback) {
return;
}
if (callback.splice) {
//callback is an array, which means it is a dependency list.
//Adjust args if there are dependencies
deps = callback;
callback = relName;
relName = null;
} else {
deps = undef;
}
}
//Support require(['a'])
callback = callback || function () {};
//If relName is a function, it is an errback handler,
//so remove it.
if (typeof relName === 'function') {
relName = forceSync;
forceSync = alt;
}
//Simulate async callback;
if (forceSync) {
main(undef, deps, callback, relName);
} else {
//Using a non-zero value because of concern for what old browsers
//do, and latest browsers "upgrade" to 4 if lower value is used:
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
//If want a value immediately, use require('id') instead -- something
//that works in almond on the global level, but not guaranteed and
//unlikely to work in other AMD implementations.
setTimeout(function () {
main(undef, deps, callback, relName);
}, 4);
}
return req;
};
/**
* Just drops the config on the floor, but returns req in case
* the config return value is used.
*/
req.config = function (cfg) {
return req(cfg);
};
/**
* Expose module registry for debugging and tooling
*/
requirejs._defined = defined;
define = function (name, deps, callback) {
if (typeof name !== 'string') {
throw new Error('See almond README: incorrect module build, no module name');
}
//This module may not have dependencies
if (!deps.splice) {
//deps is not an array, so probably means
//an object literal or factory function for
//the value. Adjust args.
callback = deps;
deps = [];
}
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
waiting[name] = [name, deps, callback];
}
};
define.amd = {
jQuery: true
};
}());
/*global define*/
define('Core/appendForwardSlash',[],function() {
'use strict';
/**
* @private
*/
function appendForwardSlash(url) {
if (url.length === 0 || url[url.length - 1] !== '/') {
url = url + '/';
}
return url;
}
return appendForwardSlash;
});
/**
@license
when.js - https://github.com/cujojs/when
MIT License (c) copyright B Cavalier & J Hann
* A lightweight CommonJS Promises/A and when() implementation
* when is part of the cujo.js family of libraries (http://cujojs.com/)
*
* Licensed under the MIT License at:
* http://www.opensource.org/licenses/mit-license.php
*
* @version 1.7.1
*/
(function(define) { 'use strict';
define('ThirdParty/when',[],function () {
var reduceArray, slice, undef;
//
// Public API
//
when.defer = defer; // Create a deferred
when.resolve = resolve; // Create a resolved promise
when.reject = reject; // Create a rejected promise
when.join = join; // Join 2 or more promises
when.all = all; // Resolve a list of promises
when.map = map; // Array.map() for promises
when.reduce = reduce; // Array.reduce() for promises
when.any = any; // One-winner race
when.some = some; // Multi-winner race
when.chain = chain; // Make a promise trigger another resolver
when.isPromise = isPromise; // Determine if a thing is a promise
/**
* Register an observer for a promise or immediate value.
*
* @param {*} promiseOrValue
* @param {function?} [onFulfilled] callback to be called when promiseOrValue is
* successfully fulfilled. If promiseOrValue is an immediate value, callback
* will be invoked immediately.
* @param {function?} [onRejected] callback to be called when promiseOrValue is
* rejected.
* @param {function?} [onProgress] callback to be called when progress updates
* are issued for promiseOrValue.
* @returns {Promise} a new {@link Promise} that will complete with the return
* value of callback or errback or the completion value of promiseOrValue if
* callback and/or errback is not supplied.
*/
function when(promiseOrValue, onFulfilled, onRejected, onProgress) {
// Get a trusted promise for the input promiseOrValue, and then
// register promise handlers
return resolve(promiseOrValue).then(onFulfilled, onRejected, onProgress);
}
/**
* Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if
* promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise}
* whose value is promiseOrValue if promiseOrValue is an immediate value.
*
* @param {*} promiseOrValue
* @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise}
* returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise}
* whose resolution value is:
* * the resolution value of promiseOrValue if it's a foreign promise, or
* * promiseOrValue if it's a value
*/
function resolve(promiseOrValue) {
var promise, deferred;
if(promiseOrValue instanceof Promise) {
// It's a when.js promise, so we trust it
promise = promiseOrValue;
} else {
// It's not a when.js promise. See if it's a foreign promise or a value.
if(isPromise(promiseOrValue)) {
// It's a thenable, but we don't know where it came from, so don't trust
// its implementation entirely. Introduce a trusted middleman when.js promise
deferred = defer();
// IMPORTANT: This is the only place when.js should ever call .then() on an
// untrusted promise. Don't expose the return value to the untrusted promise
promiseOrValue.then(
function(value) { deferred.resolve(value); },
function(reason) { deferred.reject(reason); },
function(update) { deferred.progress(update); }
);
promise = deferred.promise;
} else {
// It's a value, not a promise. Create a resolved promise for it.
promise = fulfilled(promiseOrValue);
}
}
return promise;
}
/**
* Returns a rejected promise for the supplied promiseOrValue. The returned
* promise will be rejected with:
* - promiseOrValue, if it is a value, or
* - if promiseOrValue is a promise
* - promiseOrValue's value after it is fulfilled
* - promiseOrValue's reason after it is rejected
* @param {*} promiseOrValue the rejected value of the returned {@link Promise}
* @returns {Promise} rejected {@link Promise}
*/
function reject(promiseOrValue) {
return when(promiseOrValue, rejected);
}
/**
* Trusted Promise constructor. A Promise created from this constructor is
* a trusted when.js promise. Any other duck-typed promise is considered
* untrusted.
* @constructor
* @name Promise
*/
function Promise(then) {
this.then = then;
}
Promise.prototype = {
/**
* Register a callback that will be called when a promise is
* fulfilled or rejected. Optionally also register a progress handler.
* Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress)
* @param {function?} [onFulfilledOrRejected]
* @param {function?} [onProgress]
* @returns {Promise}
*/
always: function(onFulfilledOrRejected, onProgress) {
return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress);
},
/**
* Register a rejection handler. Shortcut for .then(undefined, onRejected)
* @param {function?} onRejected
* @returns {Promise}
*/
otherwise: function(onRejected) {
return this.then(undef, onRejected);
},
/**
* Shortcut for .then(function() { return value; })
* @param {*} value
* @returns {Promise} a promise that:
* - is fulfilled if value is not a promise, or
* - if value is a promise, will fulfill with its value, or reject
* with its reason.
*/
yield: function(value) {
return this.then(function() {
return value;
});
},
/**
* Assumes that this promise will fulfill with an array, and arranges
* for the onFulfilled to be called with the array as its argument list
* i.e. onFulfilled.spread(undefined, array).
* @param {function} onFulfilled function to receive spread arguments
* @returns {Promise}
*/
spread: function(onFulfilled) {
return this.then(function(array) {
// array may contain promises, so resolve its contents.
return all(array, function(array) {
return onFulfilled.apply(undef, array);
});
});
}
};
/**
* Create an already-resolved promise for the supplied value
* @private
*
* @param {*} value
* @returns {Promise} fulfilled promise
*/
function fulfilled(value) {
var p = new Promise(function(onFulfilled) {
// TODO: Promises/A+ check typeof onFulfilled
try {
return resolve(onFulfilled ? onFulfilled(value) : value);
} catch(e) {
return rejected(e);
}
});
return p;
}
/**
* Create an already-rejected {@link Promise} with the supplied
* rejection reason.
* @private
*
* @param {*} reason
* @returns {Promise} rejected promise
*/
function rejected(reason) {
var p = new Promise(function(_, onRejected) {
// TODO: Promises/A+ check typeof onRejected
try {
return onRejected ? resolve(onRejected(reason)) : rejected(reason);
} catch(e) {
return rejected(e);
}
});
return p;
}
/**
* Creates a new, Deferred with fully isolated resolver and promise parts,
* either or both of which may be given out safely to consumers.
* The Deferred itself has the full API: resolve, reject, progress, and
* then. The resolver has resolve, reject, and progress. The promise
* only has then.
*
* @returns {Deferred}
*/
function defer() {
var deferred, promise, handlers, progressHandlers,
_then, _progress, _resolve;
/**
* The promise for the new deferred
* @type {Promise}
*/
promise = new Promise(then);
/**
* The full Deferred object, with {@link Promise} and {@link Resolver} parts
* @class Deferred
* @name Deferred
*/
deferred = {
then: then, // DEPRECATED: use deferred.promise.then
resolve: promiseResolve,
reject: promiseReject,
// TODO: Consider renaming progress() to notify()
progress: promiseProgress,
promise: promise,
resolver: {
resolve: promiseResolve,
reject: promiseReject,
progress: promiseProgress
}
};
handlers = [];
progressHandlers = [];
/**
* Pre-resolution then() that adds the supplied callback, errback, and progback
* functions to the registered listeners
* @private
*
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
*/
_then = function(onFulfilled, onRejected, onProgress) {
// TODO: Promises/A+ check typeof onFulfilled, onRejected, onProgress
var deferred, progressHandler;
deferred = defer();
progressHandler = typeof onProgress === 'function'
? function(update) {
try {
// Allow progress handler to transform progress event
deferred.progress(onProgress(update));
} catch(e) {
// Use caught value as progress
deferred.progress(e);
}
}
: function(update) { deferred.progress(update); };
handlers.push(function(promise) {
promise.then(onFulfilled, onRejected)
.then(deferred.resolve, deferred.reject, progressHandler);
});
progressHandlers.push(progressHandler);
return deferred.promise;
};
/**
* Issue a progress event, notifying all progress listeners
* @private
* @param {*} update progress event payload to pass to all listeners
*/
_progress = function(update) {
processQueue(progressHandlers, update);
return update;
};
/**
* Transition from pre-resolution state to post-resolution state, notifying
* all listeners of the resolution or rejection
* @private
* @param {*} value the value of this deferred
*/
_resolve = function(value) {
value = resolve(value);
// Replace _then with one that directly notifies with the result.
_then = value.then;
// Replace _resolve so that this Deferred can only be resolved once
_resolve = resolve;
// Make _progress a noop, to disallow progress for the resolved promise.
_progress = noop;
// Notify handlers
processQueue(handlers, value);
// Free progressHandlers array since we'll never issue progress events
progressHandlers = handlers = undef;
return value;
};
return deferred;
/**
* Wrapper to allow _then to be replaced safely
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @returns {Promise} new promise
*/
function then(onFulfilled, onRejected, onProgress) {
// TODO: Promises/A+ check typeof onFulfilled, onRejected, onProgress
return _then(onFulfilled, onRejected, onProgress);
}
/**
* Wrapper to allow _resolve to be replaced
*/
function promiseResolve(val) {
return _resolve(val);
}
/**
* Wrapper to allow _reject to be replaced
*/
function promiseReject(err) {
return _resolve(rejected(err));
}
/**
* Wrapper to allow _progress to be replaced
*/
function promiseProgress(update) {
return _progress(update);
}
}
/**
* Determines if promiseOrValue is a promise or not. Uses the feature
* test from http://wiki.commonjs.org/wiki/Promises/A to determine if
* promiseOrValue is a promise.
*
* @param {*} promiseOrValue anything
* @returns {boolean} true if promiseOrValue is a {@link Promise}
*/
function isPromise(promiseOrValue) {
return promiseOrValue && typeof promiseOrValue.then === 'function';
}
/**
* Initiates a competitive race, returning a promise that will resolve when
* howMany of the supplied promisesOrValues have resolved, or will reject when
* it becomes impossible for howMany to resolve, for example, when
* (promisesOrValues.length - howMany) + 1 input promises reject.
*
* @param {Array} promisesOrValues array of anything, may contain a mix
* of promises and values
* @param howMany {number} number of promisesOrValues to resolve
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @returns {Promise} promise that will resolve to an array of howMany values that
* resolved first, or will reject with an array of (promisesOrValues.length - howMany) + 1
* rejection reasons.
*/
function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) {
checkCallbacks(2, arguments);
return when(promisesOrValues, function(promisesOrValues) {
var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, progress, len, i;
len = promisesOrValues.length >>> 0;
toResolve = Math.max(0, Math.min(howMany, len));
values = [];
toReject = (len - toResolve) + 1;
reasons = [];
deferred = defer();
// No items in the input, resolve immediately
if (!toResolve) {
deferred.resolve(values);
} else {
progress = deferred.progress;
rejectOne = function(reason) {
reasons.push(reason);
if(!--toReject) {
fulfillOne = rejectOne = noop;
deferred.reject(reasons);
}
};
fulfillOne = function(val) {
// This orders the values based on promise resolution order
// Another strategy would be to use the original position of
// the corresponding promise.
values.push(val);
if (!--toResolve) {
fulfillOne = rejectOne = noop;
deferred.resolve(values);
}
};
for(i = 0; i < len; ++i) {
if(i in promisesOrValues) {
when(promisesOrValues[i], fulfiller, rejecter, progress);
}
}
}
return deferred.then(onFulfilled, onRejected, onProgress);
function rejecter(reason) {
rejectOne(reason);
}
function fulfiller(val) {
fulfillOne(val);
}
});
}
/**
* Initiates a competitive race, returning a promise that will resolve when
* any one of the supplied promisesOrValues has resolved or will reject when
* *all* promisesOrValues have rejected.
*
* @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @returns {Promise} promise that will resolve to the value that resolved first, or
* will reject with an array of all rejected inputs.
*/
function any(promisesOrValues, onFulfilled, onRejected, onProgress) {
function unwrapSingleResult(val) {
return onFulfilled ? onFulfilled(val[0]) : val[0];
}
return some(promisesOrValues, 1, unwrapSingleResult, onRejected, onProgress);
}
/**
* Return a promise that will resolve only once all the supplied promisesOrValues
* have resolved. The resolution value of the returned promise will be an array
* containing the resolution values of each of the promisesOrValues.
* @memberOf when
*
* @param {Array|Promise} promisesOrValues array of anything, may contain a mix
* of {@link Promise}s and values
* @param {function?} [onFulfilled] resolution handler
* @param {function?} [onRejected] rejection handler
* @param {function?} [onProgress] progress handler
* @returns {Promise}
*/
function all(promisesOrValues, onFulfilled, onRejected, onProgress) {
checkCallbacks(1, arguments);
return map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress);
}
/**
* Joins multiple promises into a single returned promise.
* @returns {Promise} a promise that will fulfill when *all* the input promises
* have fulfilled, or will reject when *any one* of the input promises rejects.
*/
function join(/* ...promises */) {
return map(arguments, identity);
}
/**
* Traditional map function, similar to `Array.prototype.map()`, but allows
* input to contain {@link Promise}s and/or values, and mapFunc may return
* either a value or a {@link Promise}
*
* @param {Array|Promise} promise array of anything, may contain a mix
* of {@link Promise}s and values
* @param {function} mapFunc mapping function mapFunc(value) which may return
* either a {@link Promise} or value
* @returns {Promise} a {@link Promise} that will resolve to an array containing
* the mapped output values.
*/
function map(promise, mapFunc) {
return when(promise, function(array) {
var results, len, toResolve, resolve, i, d;
// Since we know the resulting length, we can preallocate the results
// array to avoid array expansions.
toResolve = len = array.length >>> 0;
results = [];
d = defer();
if(!toResolve) {
d.resolve(results);
} else {
resolve = function resolveOne(item, i) {
when(item, mapFunc).then(function(mapped) {
results[i] = mapped;
if(!--toResolve) {
d.resolve(results);
}
}, d.reject);
};
// Since mapFunc may be async, get all invocations of it into flight
for(i = 0; i < len; i++) {
if(i in array) {
resolve(array[i], i);
} else {
--toResolve;
}
}
}
return d.promise;
});
}
/**
* Traditional reduce function, similar to `Array.prototype.reduce()`, but
* input may contain promises and/or values, and reduceFunc
* may return either a value or a promise, *and* initialValue may
* be a promise for the starting value.
*
* @param {Array|Promise} promise array or promise for an array of anything,
* may contain a mix of promises and values.
* @param {function} reduceFunc reduce function reduce(currentValue, nextValue, index, total),
* where total is the total number of items being reduced, and will be the same
* in each call to reduceFunc.
* @returns {Promise} that will resolve to the final reduced value
*/
function reduce(promise, reduceFunc /*, initialValue */) {
var args = slice.call(arguments, 1);
return when(promise, function(array) {
var total;
total = array.length;
// Wrap the supplied reduceFunc with one that handles promises and then
// delegates to the supplied.
args[0] = function (current, val, i) {
return when(current, function (c) {
return when(val, function (value) {
return reduceFunc(c, value, i, total);
});
});
};
return reduceArray.apply(array, args);
});
}
/**
* Ensure that resolution of promiseOrValue will trigger resolver with the
* value or reason of promiseOrValue, or instead with resolveValue if it is provided.
*
* @param promiseOrValue
* @param {Object} resolver
* @param {function} resolver.resolve
* @param {function} resolver.reject
* @param {*} [resolveValue]
* @returns {Promise}
*/
function chain(promiseOrValue, resolver, resolveValue) {
var useResolveValue = arguments.length > 2;
return when(promiseOrValue,
function(val) {
val = useResolveValue ? resolveValue : val;
resolver.resolve(val);
return val;
},
function(reason) {
resolver.reject(reason);
return rejected(reason);
},
resolver.progress
);
}
//
// Utility functions
//
/**
* Apply all functions in queue to value
* @param {Array} queue array of functions to execute
* @param {*} value argument passed to each function
*/
function processQueue(queue, value) {
var handler, i = 0;
while (handler = queue[i++]) {
handler(value);
}
}
/**
* Helper that checks arrayOfCallbacks to ensure that each element is either
* a function, or null or undefined.
* @private
* @param {number} start index at which to start checking items in arrayOfCallbacks
* @param {Array} arrayOfCallbacks array to check
* @throws {Error} if any element of arrayOfCallbacks is something other than
* a functions, null, or undefined.
*/
function checkCallbacks(start, arrayOfCallbacks) {
// TODO: Promises/A+ update type checking and docs
var arg, i = arrayOfCallbacks.length;
while(i > start) {
arg = arrayOfCallbacks[--i];
if (arg != null && typeof arg != 'function') {
throw new Error('arg '+i+' must be a function');
}
}
}
/**
* No-Op function used in method replacement
* @private
*/
function noop() {}
slice = [].slice;
// ES5 reduce implementation if native not available
// See: http://es5.github.com/#x15.4.4.21 as there are many
// specifics and edge cases.
reduceArray = [].reduce ||
function(reduceFunc /*, initialValue */) {
/*jshint maxcomplexity: 7*/
// ES5 dictates that reduce.length === 1
// This implementation deviates from ES5 spec in the following ways:
// 1. It does not check if reduceFunc is a Callable
var arr, args, reduced, len, i;
i = 0;
// This generates a jshint warning, despite being valid
// "Missing 'new' prefix when invoking a constructor."
// See https://github.com/jshint/jshint/issues/392
arr = Object(this);
len = arr.length >>> 0;
args = arguments;
// If no initialValue, use first item of array (we know length !== 0 here)
// and adjust i to start at second item
if(args.length <= 1) {
// Skip to the first real element in the array
for(;;) {
if(i in arr) {
reduced = arr[i++];
break;
}
// If we reached the end of the array without finding any real
// elements, it's a TypeError
if(++i >= len) {
throw new TypeError();
}
}
} else {
// If initialValue provided, use it
reduced = args[1];
}
// Do the actual reduce
for(;i < len; ++i) {
// Skip holes
if(i in arr) {
reduced = reduceFunc(reduced, arr[i], i, arr);
}
}
return reduced;
};
function identity(x) {
return x;
}
return when;
});
})(typeof define == 'function' && define.amd
? define
: function (factory) { typeof exports === 'object'
? (module.exports = factory())
: (this.when = factory());
}
// Boilerplate for AMD, Node, and browser global
);
/*global define*/
define('Core/defined',[],function() {
'use strict';
/**
* @exports defined
*
* @param {Object} value The object.
* @returns {Boolean} Returns true if the object is defined, returns false otherwise.
*
* @example
* if (Cesium.defined(positions)) {
* doSomething();
* } else {
* doSomethingElse();
* }
*/
function defined(value) {
return value !== undefined && value !== null;
}
return defined;
});
/*global define*/
define('Core/defineProperties',[
'./defined'
], function(
defined) {
'use strict';
var definePropertyWorks = (function() {
try {
return 'x' in Object.defineProperty({}, 'x', {});
} catch (e) {
return false;
}
})();
/**
* Defines properties on an object, using Object.defineProperties if available,
* otherwise returns the object unchanged. This function should be used in
* setup code to prevent errors from completely halting JavaScript execution
* in legacy browsers.
*
* @private
*
* @exports defineProperties
*/
var defineProperties = Object.defineProperties;
if (!definePropertyWorks || !defined(defineProperties)) {
defineProperties = function(o) {
return o;
};
}
return defineProperties;
});
/*global define*/
define('Core/DeveloperError',[
'./defined'
], function(
defined) {
'use strict';
/**
* Constructs an exception object that is thrown due to a developer error, e.g., invalid argument,
* argument out of range, etc. This exception should only be thrown during development;
* it usually indicates a bug in the calling code. This exception should never be
* caught; instead the calling code should strive not to generate it.
*
* On the other hand, a {@link RuntimeError} indicates an exception that may
* be thrown at runtime, e.g., out of memory, that the calling code should be prepared
* to catch.
*
* @alias DeveloperError
* @constructor
* @extends Error
*
* @param {String} [message] The error message for this exception.
*
* @see RuntimeError
*/
function DeveloperError(message) {
/**
* 'DeveloperError' indicating that this exception was thrown due to a developer error.
* @type {String}
* @readonly
*/
this.name = 'DeveloperError';
/**
* The explanation for why this exception was thrown.
* @type {String}
* @readonly
*/
this.message = message;
//Browsers such as IE don't have a stack property until you actually throw the error.
var stack;
try {
throw new Error();
} catch (e) {
stack = e.stack;
}
/**
* The stack trace of this exception, if available.
* @type {String}
* @readonly
*/
this.stack = stack;
}
if (defined(Object.create)) {
DeveloperError.prototype = Object.create(Error.prototype);
DeveloperError.prototype.constructor = DeveloperError;
}
DeveloperError.prototype.toString = function() {
var str = this.name + ': ' + this.message;
if (defined(this.stack)) {
str += '\n' + this.stack.toString();
}
return str;
};
/**
* @private
*/
DeveloperError.throwInstantiationError = function() {
throw new DeveloperError('This function defines an interface and should not be called directly.');
};
return DeveloperError;
});
/*global define*/
define('Core/Credit',[
'./defined',
'./defineProperties',
'./DeveloperError'
], function(
defined,
defineProperties,
DeveloperError) {
'use strict';
var nextCreditId = 0;
var creditToId = {};
/**
* A credit contains data pertaining to how to display attributions/credits for certain content on the screen.
*
* @param {String} [text] The text to be displayed on the screen if no imageUrl is specified.
* @param {String} [imageUrl] The source location for an image
* @param {String} [link] A URL location for which the credit will be hyperlinked
*
* @alias Credit
* @constructor
*
* @example
* //Create a credit with a tooltip, image and link
* var credit = new Cesium.Credit('Cesium', '/images/cesium_logo.png', 'http://cesiumjs.org/');
*/
function Credit(text, imageUrl, link) {
var hasLink = (defined(link));
var hasImage = (defined(imageUrl));
var hasText = (defined(text));
if (!hasText && !hasImage && !hasLink) {
throw new DeveloperError('text, imageUrl or link is required');
}
if (!hasText && !hasImage) {
text = link;
}
this._text = text;
this._imageUrl = imageUrl;
this._link = link;
this._hasLink = hasLink;
this._hasImage = hasImage;
// Credits are immutable so generate an id to use to optimize equal()
var id;
var key = JSON.stringify([text, imageUrl, link]);
if (defined(creditToId[key])) {
id = creditToId[key];
} else {
id = nextCreditId++;
creditToId[key] = id;
}
this._id = id;
}
defineProperties(Credit.prototype, {
/**
* The credit text
* @memberof Credit.prototype
* @type {String}
* @readonly
*/
text : {
get : function() {
return this._text;
}
},
/**
* The source location for the image.
* @memberof Credit.prototype
* @type {String}
* @readonly
*/
imageUrl : {
get : function() {
return this._imageUrl;
}
},
/**
* A URL location for the credit hyperlink
* @memberof Credit.prototype
* @type {String}
* @readonly
*/
link : {
get : function() {
return this._link;
}
},
/**
* @memberof Credit.prototype
* @type {Number}
* @readonly
*
* @private
*/
id : {
get : function() {
return this._id;
}
}
});
/**
* Returns true if the credit has an imageUrl
*
* @returns {Boolean}
*/
Credit.prototype.hasImage = function() {
return this._hasImage;
};
/**
* Returns true if the credit has a link
*
* @returns {Boolean}
*/
Credit.prototype.hasLink = function() {
return this._hasLink;
};
/**
* Returns true if the credits are equal
*
* @param {Credit} left The first credit
* @param {Credit} right The second credit
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Credit.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left._id === right._id));
};
/**
* Returns true if the credits are equal
*
* @param {Credit} credits The credit to compare to.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Credit.prototype.equals = function(credit) {
return Credit.equals(this, credit);
};
return Credit;
});
/*global define*/
define('Core/freezeObject',[
'./defined'
], function(
defined) {
'use strict';
/**
* Freezes an object, using Object.freeze if available, otherwise returns
* the object unchanged. This function should be used in setup code to prevent
* errors from completely halting JavaScript execution in legacy browsers.
*
* @private
*
* @exports freezeObject
*/
var freezeObject = Object.freeze;
if (!defined(freezeObject)) {
freezeObject = function(o) {
return o;
};
}
return freezeObject;
});
/*global define*/
define('Core/defaultValue',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Returns the first parameter if not undefined, otherwise the second parameter.
* Useful for setting a default value for a parameter.
*
* @exports defaultValue
*
* @param {*} a
* @param {*} b
* @returns {*} Returns the first parameter if not undefined, otherwise the second parameter.
*
* @example
* param = Cesium.defaultValue(param, 'default');
*/
function defaultValue(a, b) {
if (a !== undefined) {
return a;
}
return b;
}
/**
* A frozen empty object that can be used as the default value for options passed as
* an object literal.
*/
defaultValue.EMPTY_OBJECT = freezeObject({});
return defaultValue;
});
/*global define*/
define('Core/isArray',[
'./defined'
], function(
defined) {
'use strict';
/**
* Tests an object to see if it is an array.
* @exports isArray
*
* @param {Object} value The value to test.
* @returns {Boolean} true if the value is an array, false otherwise.
*/
var isArray = Array.isArray;
if (!defined(isArray)) {
isArray = function(value) {
return Object.prototype.toString.call(value) === '[object Array]';
};
}
return isArray;
});
/*global define*/
define('Core/Check',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./isArray'
], function(
defaultValue,
defined,
DeveloperError,
isArray) {
'use strict';
/**
* Contains functions for checking that supplied arguments are of a specified type
* or meet specified conditions
* @private
*/
var Check = {};
/**
* Contains type checking functions, all using the typeof operator
*/
Check.typeOf = {};
/**
* Contains functions for checking numeric conditions such as minimum and maximum values
*/
Check.numeric = {};
function getUndefinedErrorMessage(name) {
return name + ' was required but undefined.';
}
function getFailedTypeErrorMessage(actual, expected, name) {
return 'Expected ' + name + ' to be typeof ' + expected + ', got ' + actual;
}
/**
* Throws if test is not defined
*
* @param {*} test The value that is to be checked
* @param {String} name The name of the variable being tested
* @exception {DeveloperError} test must be defined
*/
Check.defined = function (test, name) {
if (!defined(test)) {
throw new DeveloperError(getUndefinedErrorMessage(name));
}
};
/**
* Throws if test is greater than maximum
*
* @param {Number} test The value to test
* @param {Number} maximum The maximum allowed value
* @exception {DeveloperError} test must not be greater than maximum
* @exception {DeveloperError} Both test and maximum must be typeof 'number'
*/
Check.numeric.maximum = function (test, maximum) {
Check.typeOf.number(test);
Check.typeOf.number(maximum);
if (test > maximum) {
throw new DeveloperError('Expected ' + test + ' to be at most ' + maximum);
}
};
/**
* Throws if test is less than minimum
*
* @param {Number} test The value to test
* @param {Number} minimum The minimum allowed value
* @exception {DeveloperError} test must not be less than mininum
* @exception {DeveloperError} Both test and maximum must be typeof 'number'
*/
Check.numeric.minimum = function (test, minimum) {
Check.typeOf.number(test);
Check.typeOf.number(minimum);
if (test < minimum) {
throw new DeveloperError('Expected ' + test + ' to be at least ' + minimum);
}
};
/**
* Throws if test is not typeof 'function'
*
* @param {*} test The value to test
* @param {String} name The name of the variable being tested
* @exception {DeveloperError} test must be typeof 'function'
*/
Check.typeOf.function = function (test, name) {
if (typeof test !== 'function') {
throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'function', name));
}
};
/**
* Throws if test is not typeof 'string'
*
* @param {*} test The value to test
* @param {String} name The name of the variable being tested
* @exception {DeveloperError} test must be typeof 'string'
*/
Check.typeOf.string = function (test, name) {
if (typeof test !== 'string') {
throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'string', name));
}
};
/**
* Throws if test is not typeof 'number'
*
* @param {*} test The value to test
* @param {String} name The name of the variable being tested
* @exception {DeveloperError} test must be typeof 'number'
*/
Check.typeOf.number = function (test, name) {
if (typeof test !== 'number') {
throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'number', name));
}
};
/**
* Throws if test is not typeof 'object'
*
* @param {*} test The value to test
* @param {String} name The name of the variable being tested
* @exception {DeveloperError} test must be typeof 'object'
*/
Check.typeOf.object = function (test, name) {
if (typeof test !== 'object') {
throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'object', name));
}
};
/**
* Throws if test is not typeof 'boolean'
*
* @param {*} test The value to test
* @param {String} name The name of the variable being tested
* @exception {DeveloperError} test must be typeof 'boolean'
*/
Check.typeOf.boolean = function (test, name) {
if (typeof test !== 'boolean') {
throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'boolean', name));
}
};
return Check;
});
/*
I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
so it's better encapsulated. Now you can have multiple random number generators
and they won't stomp all over eachother's state.
If you want to use this as a substitute for Math.random(), use the random()
method like so:
var m = new MersenneTwister();
var randomNumber = m.random();
You can also call the other genrand_{foo}() methods on the instance.
If you want to use a specific seed in order to get a repeatable random
sequence, pass an integer into the constructor:
var m = new MersenneTwister(123);
and that will always produce the same random sequence.
Sean McCullough (banksean@gmail.com)
*/
/*
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
*/
/**
@license
mersenne-twister.js - https://gist.github.com/banksean/300494
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Any feedback is very welcome.
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
define('ThirdParty/mersenne-twister',[],function() {
var MersenneTwister = function(seed) {
if (seed == undefined) {
seed = new Date().getTime();
}
/* Period parameters */
this.N = 624;
this.M = 397;
this.MATRIX_A = 0x9908b0df; /* constant vector a */
this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
this.LOWER_MASK = 0x7fffffff; /* least significant r bits */
this.mt = new Array(this.N); /* the array for the state vector */
this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */
this.init_genrand(seed);
}
/* initializes mt[N] with a seed */
MersenneTwister.prototype.init_genrand = function(s) {
this.mt[0] = s >>> 0;
for (this.mti=1; this.mti>> 30);
this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
+ this.mti;
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
this.mt[this.mti] >>>= 0;
/* for >32 bit machines */
}
}
/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
//MersenneTwister.prototype.init_by_array = function(init_key, key_length) {
// var i, j, k;
// this.init_genrand(19650218);
// i=1; j=0;
// k = (this.N>key_length ? this.N : key_length);
// for (; k; k--) {
// var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30)
// this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
// + init_key[j] + j; /* non linear */
// this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
// i++; j++;
// if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
// if (j>=key_length) j=0;
// }
// for (k=this.N-1; k; k--) {
// var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
// this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
// - i; /* non linear */
// this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
// i++;
// if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
// }
//
// this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
//}
/* generates a random number on [0,0xffffffff]-interval */
MersenneTwister.prototype.genrand_int32 = function() {
var y;
var mag01 = new Array(0x0, this.MATRIX_A);
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (this.mti >= this.N) { /* generate N words at one time */
var kk;
if (this.mti == this.N+1) /* if init_genrand() has not been called, */
this.init_genrand(5489); /* a default initial seed is used */
for (kk=0;kk>> 1) ^ mag01[y & 0x1];
}
for (;kk>> 1) ^ mag01[y & 0x1];
}
y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
this.mti = 0;
}
y = this.mt[this.mti++];
/* Tempering */
y ^= (y >>> 11);
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= (y >>> 18);
return y >>> 0;
}
/* generates a random number on [0,0x7fffffff]-interval */
//MersenneTwister.prototype.genrand_int31 = function() {
// return (this.genrand_int32()>>>1);
//}
/* generates a random number on [0,1]-real-interval */
//MersenneTwister.prototype.genrand_real1 = function() {
// return this.genrand_int32()*(1.0/4294967295.0);
// /* divided by 2^32-1 */
//}
/* generates a random number on [0,1)-real-interval */
MersenneTwister.prototype.random = function() {
return this.genrand_int32()*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on (0,1)-real-interval */
//MersenneTwister.prototype.genrand_real3 = function() {
// return (this.genrand_int32() + 0.5)*(1.0/4294967296.0);
// /* divided by 2^32 */
//}
/* generates a random number on [0,1) with 53-bit resolution*/
//MersenneTwister.prototype.genrand_res53 = function() {
// var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
// return(a*67108864.0+b)*(1.0/9007199254740992.0);
//}
/* These real versions are due to Isaku Wada, 2002/01/09 added */
return MersenneTwister;
});
/*global define*/
define('Core/Math',[
'../ThirdParty/mersenne-twister',
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
MersenneTwister,
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Math functions.
*
* @exports CesiumMath
*/
var CesiumMath = {};
/**
* 0.1
* @type {Number}
* @constant
*/
CesiumMath.EPSILON1 = 0.1;
/**
* 0.01
* @type {Number}
* @constant
*/
CesiumMath.EPSILON2 = 0.01;
/**
* 0.001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON3 = 0.001;
/**
* 0.0001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON4 = 0.0001;
/**
* 0.00001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON5 = 0.00001;
/**
* 0.000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON6 = 0.000001;
/**
* 0.0000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON7 = 0.0000001;
/**
* 0.00000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON8 = 0.00000001;
/**
* 0.000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON9 = 0.000000001;
/**
* 0.0000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON10 = 0.0000000001;
/**
* 0.00000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON11 = 0.00000000001;
/**
* 0.000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON12 = 0.000000000001;
/**
* 0.0000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON13 = 0.0000000000001;
/**
* 0.00000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON14 = 0.00000000000001;
/**
* 0.000000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON15 = 0.000000000000001;
/**
* 0.0000000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON16 = 0.0000000000000001;
/**
* 0.00000000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON17 = 0.00000000000000001;
/**
* 0.000000000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON18 = 0.000000000000000001;
/**
* 0.0000000000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON19 = 0.0000000000000000001;
/**
* 0.00000000000000000001
* @type {Number}
* @constant
*/
CesiumMath.EPSILON20 = 0.00000000000000000001;
/**
* 3.986004418e14
* @type {Number}
* @constant
*/
CesiumMath.GRAVITATIONALPARAMETER = 3.986004418e14;
/**
* Radius of the sun in meters: 6.955e8
* @type {Number}
* @constant
*/
CesiumMath.SOLAR_RADIUS = 6.955e8;
/**
* The mean radius of the moon, according to the "Report of the IAU/IAG Working Group on
* Cartographic Coordinates and Rotational Elements of the Planets and satellites: 2000",
* Celestial Mechanics 82: 83-110, 2002.
* @type {Number}
* @constant
*/
CesiumMath.LUNAR_RADIUS = 1737400.0;
/**
* 64 * 1024
* @type {Number}
* @constant
*/
CesiumMath.SIXTY_FOUR_KILOBYTES = 64 * 1024;
/**
* Returns the sign of the value; 1 if the value is positive, -1 if the value is
* negative, or 0 if the value is 0.
*
* @param {Number} value The value to return the sign of.
* @returns {Number} The sign of value.
*/
CesiumMath.sign = function(value) {
if (value > 0) {
return 1;
}
if (value < 0) {
return -1;
}
return 0;
};
/**
* Returns 1.0 if the given value is positive or zero, and -1.0 if it is negative.
* This is similar to {@link CesiumMath#sign} except that returns 1.0 instead of
* 0.0 when the input value is 0.0.
* @param {Number} value The value to return the sign of.
* @returns {Number} The sign of value.
*/
CesiumMath.signNotZero = function(value) {
return value < 0.0 ? -1.0 : 1.0;
};
/**
* Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the range [0, rangeMax]
* @param {Number} value The scalar value in the range [-1.0, 1.0]
* @param {Number} [rangeMax=255] The maximum value in the mapped range, 255 by default.
* @returns {Number} A SNORM value, where 0 maps to -1.0 and rangeMax maps to 1.0.
*
* @see CesiumMath.fromSNorm
*/
CesiumMath.toSNorm = function(value, rangeMax) {
rangeMax = defaultValue(rangeMax, 255);
return Math.round((CesiumMath.clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMax);
};
/**
* Converts a SNORM value in the range [0, rangeMax] to a scalar in the range [-1.0, 1.0].
* @param {Number} value SNORM value in the range [0, 255]
* @param {Number} [rangeMax=255] The maximum value in the SNORM range, 255 by default.
* @returns {Number} Scalar in the range [-1.0, 1.0].
*
* @see CesiumMath.toSNorm
*/
CesiumMath.fromSNorm = function(value, rangeMax) {
rangeMax = defaultValue(rangeMax, 255);
return CesiumMath.clamp(value, 0.0, rangeMax) / rangeMax * 2.0 - 1.0;
};
/**
* Returns the hyperbolic sine of a number.
* The hyperbolic sine of value is defined to be
* (ex - e-x )/2.0
* where e is Euler's number, approximately 2.71828183.
*
* Special cases:
*
* If the argument is NaN, then the result is NaN.
*
* If the argument is infinite, then the result is an infinity
* with the same sign as the argument.
*
* If the argument is zero, then the result is a zero with the
* same sign as the argument.
*
*
*
* @param {Number} value The number whose hyperbolic sine is to be returned.
* @returns {Number} The hyperbolic sine of value
.
*/
CesiumMath.sinh = function(value) {
var part1 = Math.pow(Math.E, value);
var part2 = Math.pow(Math.E, -1.0 * value);
return (part1 - part2) * 0.5;
};
/**
* Returns the hyperbolic cosine of a number.
* The hyperbolic cosine of value is defined to be
* (ex + e-x )/2.0
* where e is Euler's number, approximately 2.71828183.
*
* Special cases:
*
* If the argument is NaN, then the result is NaN.
*
* If the argument is infinite, then the result is positive infinity.
*
* If the argument is zero, then the result is 1.0.
*
*
*
* @param {Number} value The number whose hyperbolic cosine is to be returned.
* @returns {Number} The hyperbolic cosine of value
.
*/
CesiumMath.cosh = function(value) {
var part1 = Math.pow(Math.E, value);
var part2 = Math.pow(Math.E, -1.0 * value);
return (part1 + part2) * 0.5;
};
/**
* Computes the linear interpolation of two values.
*
* @param {Number} p The start value to interpolate.
* @param {Number} q The end value to interpolate.
* @param {Number} time The time of interpolation generally in the range [0.0, 1.0]
.
* @returns {Number} The linearly interpolated value.
*
* @example
* var n = Cesium.Math.lerp(0.0, 2.0, 0.5); // returns 1.0
*/
CesiumMath.lerp = function(p, q, time) {
return ((1.0 - time) * p) + (time * q);
};
/**
* pi
*
* @type {Number}
* @constant
*/
CesiumMath.PI = Math.PI;
/**
* 1/pi
*
* @type {Number}
* @constant
*/
CesiumMath.ONE_OVER_PI = 1.0 / Math.PI;
/**
* pi/2
*
* @type {Number}
* @constant
*/
CesiumMath.PI_OVER_TWO = Math.PI * 0.5;
/**
* pi/3
*
* @type {Number}
* @constant
*/
CesiumMath.PI_OVER_THREE = Math.PI / 3.0;
/**
* pi/4
*
* @type {Number}
* @constant
*/
CesiumMath.PI_OVER_FOUR = Math.PI / 4.0;
/**
* pi/6
*
* @type {Number}
* @constant
*/
CesiumMath.PI_OVER_SIX = Math.PI / 6.0;
/**
* 3pi/2
*
* @type {Number}
* @constant
*/
CesiumMath.THREE_PI_OVER_TWO = (3.0 * Math.PI) * 0.5;
/**
* 2pi
*
* @type {Number}
* @constant
*/
CesiumMath.TWO_PI = 2.0 * Math.PI;
/**
* 1/2pi
*
* @type {Number}
* @constant
*/
CesiumMath.ONE_OVER_TWO_PI = 1.0 / (2.0 * Math.PI);
/**
* The number of radians in a degree.
*
* @type {Number}
* @constant
* @default Math.PI / 180.0
*/
CesiumMath.RADIANS_PER_DEGREE = Math.PI / 180.0;
/**
* The number of degrees in a radian.
*
* @type {Number}
* @constant
* @default 180.0 / Math.PI
*/
CesiumMath.DEGREES_PER_RADIAN = 180.0 / Math.PI;
/**
* The number of radians in an arc second.
*
* @type {Number}
* @constant
* @default {@link CesiumMath.RADIANS_PER_DEGREE} / 3600.0
*/
CesiumMath.RADIANS_PER_ARCSECOND = CesiumMath.RADIANS_PER_DEGREE / 3600.0;
/**
* Converts degrees to radians.
* @param {Number} degrees The angle to convert in degrees.
* @returns {Number} The corresponding angle in radians.
*/
CesiumMath.toRadians = function(degrees) {
if (!defined(degrees)) {
throw new DeveloperError('degrees is required.');
}
return degrees * CesiumMath.RADIANS_PER_DEGREE;
};
/**
* Converts radians to degrees.
* @param {Number} radians The angle to convert in radians.
* @returns {Number} The corresponding angle in degrees.
*/
CesiumMath.toDegrees = function(radians) {
if (!defined(radians)) {
throw new DeveloperError('radians is required.');
}
return radians * CesiumMath.DEGREES_PER_RADIAN;
};
/**
* Converts a longitude value, in radians, to the range [-Math.PI
, Math.PI
).
*
* @param {Number} angle The longitude value, in radians, to convert to the range [-Math.PI
, Math.PI
).
* @returns {Number} The equivalent longitude value in the range [-Math.PI
, Math.PI
).
*
* @example
* // Convert 270 degrees to -90 degrees longitude
* var longitude = Cesium.Math.convertLongitudeRange(Cesium.Math.toRadians(270.0));
*/
CesiumMath.convertLongitudeRange = function(angle) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
var twoPi = CesiumMath.TWO_PI;
var simplified = angle - Math.floor(angle / twoPi) * twoPi;
if (simplified < -Math.PI) {
return simplified + twoPi;
}
if (simplified >= Math.PI) {
return simplified - twoPi;
}
return simplified;
};
/**
* Convenience function that clamps a latitude value, in radians, to the range [-Math.PI/2
, Math.PI/2
).
* Useful for sanitizing data before use in objects requiring correct range.
*
* @param {Number} angle The latitude value, in radians, to clamp to the range [-Math.PI/2
, Math.PI/2
).
* @returns {Number} The latitude value clamped to the range [-Math.PI/2
, Math.PI/2
).
*
* @example
* // Clamp 108 degrees latitude to 90 degrees latitude
* var latitude = Cesium.Math.clampToLatitudeRange(Cesium.Math.toRadians(108.0));
*/
CesiumMath.clampToLatitudeRange = function(angle) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
return CesiumMath.clamp(angle, -1*CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO);
};
/**
* Produces an angle in the range -Pi <= angle <= Pi which is equivalent to the provided angle.
*
* @param {Number} angle in radians
* @returns {Number} The angle in the range [-CesiumMath.PI
, CesiumMath.PI
].
*/
CesiumMath.negativePiToPi = function(x) {
if (!defined(x)) {
throw new DeveloperError('x is required.');
}
return CesiumMath.zeroToTwoPi(x + CesiumMath.PI) - CesiumMath.PI;
};
/**
* Produces an angle in the range 0 <= angle <= 2Pi which is equivalent to the provided angle.
*
* @param {Number} angle in radians
* @returns {Number} The angle in the range [0, CesiumMath.TWO_PI
].
*/
CesiumMath.zeroToTwoPi = function(x) {
if (!defined(x)) {
throw new DeveloperError('x is required.');
}
var mod = CesiumMath.mod(x, CesiumMath.TWO_PI);
if (Math.abs(mod) < CesiumMath.EPSILON14 && Math.abs(x) > CesiumMath.EPSILON14) {
return CesiumMath.TWO_PI;
}
return mod;
};
/**
* The modulo operation that also works for negative dividends.
*
* @param {Number} m The dividend.
* @param {Number} n The divisor.
* @returns {Number} The remainder.
*/
CesiumMath.mod = function(m, n) {
if (!defined(m)) {
throw new DeveloperError('m is required.');
}
if (!defined(n)) {
throw new DeveloperError('n is required.');
}
return ((m % n) + n) % n;
};
/**
* Determines if two values are equal using an absolute or relative tolerance test. This is useful
* to avoid problems due to roundoff error when comparing floating-point values directly. The values are
* first compared using an absolute tolerance test. If that fails, a relative tolerance test is performed.
* Use this test if you are unsure of the magnitudes of left and right.
*
* @param {Number} left The first value to compare.
* @param {Number} right The other value to compare.
* @param {Number} relativeEpsilon The maximum inclusive delta between left
and right
for the relative tolerance test.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The maximum inclusive delta between left
and right
for the absolute tolerance test.
* @returns {Boolean} true
if the values are equal within the epsilon; otherwise, false
.
*
* @example
* var a = Cesium.Math.equalsEpsilon(0.0, 0.01, Cesium.Math.EPSILON2); // true
* var b = Cesium.Math.equalsEpsilon(0.0, 0.1, Cesium.Math.EPSILON2); // false
* var c = Cesium.Math.equalsEpsilon(3699175.1634344, 3699175.2, Cesium.Math.EPSILON7); // true
* var d = Cesium.Math.equalsEpsilon(3699175.1634344, 3699175.2, Cesium.Math.EPSILON9); // false
*/
CesiumMath.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
if (!defined(relativeEpsilon)) {
throw new DeveloperError('relativeEpsilon is required.');
}
absoluteEpsilon = defaultValue(absoluteEpsilon, relativeEpsilon);
var absDiff = Math.abs(left - right);
return absDiff <= absoluteEpsilon || absDiff <= relativeEpsilon * Math.max(Math.abs(left), Math.abs(right));
};
var factorials = [1];
/**
* Computes the factorial of the provided number.
*
* @param {Number} n The number whose factorial is to be computed.
* @returns {Number} The factorial of the provided number or undefined if the number is less than 0.
*
* @exception {DeveloperError} A number greater than or equal to 0 is required.
*
*
* @example
* //Compute 7!, which is equal to 5040
* var computedFactorial = Cesium.Math.factorial(7);
*
* @see {@link http://en.wikipedia.org/wiki/Factorial|Factorial on Wikipedia}
*/
CesiumMath.factorial = function(n) {
if (typeof n !== 'number' || n < 0) {
throw new DeveloperError('A number greater than or equal to 0 is required.');
}
var length = factorials.length;
if (n >= length) {
var sum = factorials[length - 1];
for (var i = length; i <= n; i++) {
factorials.push(sum * i);
}
}
return factorials[n];
};
/**
* Increments a number with a wrapping to a minimum value if the number exceeds the maximum value.
*
* @param {Number} [n] The number to be incremented.
* @param {Number} [maximumValue] The maximum incremented value before rolling over to the minimum value.
* @param {Number} [minimumValue=0.0] The number reset to after the maximum value has been exceeded.
* @returns {Number} The incremented number.
*
* @exception {DeveloperError} Maximum value must be greater than minimum value.
*
* @example
* var n = Cesium.Math.incrementWrap(5, 10, 0); // returns 6
* var n = Cesium.Math.incrementWrap(10, 10, 0); // returns 0
*/
CesiumMath.incrementWrap = function(n, maximumValue, minimumValue) {
minimumValue = defaultValue(minimumValue, 0.0);
if (!defined(n)) {
throw new DeveloperError('n is required.');
}
if (maximumValue <= minimumValue) {
throw new DeveloperError('maximumValue must be greater than minimumValue.');
}
++n;
if (n > maximumValue) {
n = minimumValue;
}
return n;
};
/**
* Determines if a positive integer is a power of two.
*
* @param {Number} n The positive integer to test.
* @returns {Boolean} true
if the number if a power of two; otherwise, false
.
*
* @exception {DeveloperError} A number greater than or equal to 0 is required.
*
* @example
* var t = Cesium.Math.isPowerOfTwo(16); // true
* var f = Cesium.Math.isPowerOfTwo(20); // false
*/
CesiumMath.isPowerOfTwo = function(n) {
if (typeof n !== 'number' || n < 0) {
throw new DeveloperError('A number greater than or equal to 0 is required.');
}
return (n !== 0) && ((n & (n - 1)) === 0);
};
/**
* Computes the next power-of-two integer greater than or equal to the provided positive integer.
*
* @param {Number} n The positive integer to test.
* @returns {Number} The next power-of-two integer.
*
* @exception {DeveloperError} A number greater than or equal to 0 is required.
*
* @example
* var n = Cesium.Math.nextPowerOfTwo(29); // 32
* var m = Cesium.Math.nextPowerOfTwo(32); // 32
*/
CesiumMath.nextPowerOfTwo = function(n) {
if (typeof n !== 'number' || n < 0) {
throw new DeveloperError('A number greater than or equal to 0 is required.');
}
// From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
--n;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
++n;
return n;
};
/**
* Constraint a value to lie between two values.
*
* @param {Number} value The value to constrain.
* @param {Number} min The minimum value.
* @param {Number} max The maximum value.
* @returns {Number} The value clamped so that min <= value <= max.
*/
CesiumMath.clamp = function(value, min, max) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(min)) {
throw new DeveloperError('min is required.');
}
if (!defined(max)) {
throw new DeveloperError('max is required.');
}
return value < min ? min : value > max ? max : value;
};
var randomNumberGenerator = new MersenneTwister();
/**
* Sets the seed used by the random number generator
* in {@link CesiumMath#nextRandomNumber}.
*
* @param {Number} seed An integer used as the seed.
*/
CesiumMath.setRandomNumberSeed = function(seed) {
if (!defined(seed)) {
throw new DeveloperError('seed is required.');
}
randomNumberGenerator = new MersenneTwister(seed);
};
/**
* Generates a random number in the range of [0.0, 1.0)
* using a Mersenne twister.
*
* @returns {Number} A random number in the range of [0.0, 1.0).
*
* @see CesiumMath.setRandomNumberSeed
* @see {@link http://en.wikipedia.org/wiki/Mersenne_twister|Mersenne twister on Wikipedia}
*/
CesiumMath.nextRandomNumber = function() {
return randomNumberGenerator.random();
};
/**
* Computes Math.acos(value), but first clamps value
to the range [-1.0, 1.0]
* so that the function will never return NaN.
*
* @param {Number} value The value for which to compute acos.
* @returns {Number} The acos of the value if the value is in the range [-1.0, 1.0], or the acos of -1.0 or 1.0,
* whichever is closer, if the value is outside the range.
*/
CesiumMath.acosClamped = function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
return Math.acos(CesiumMath.clamp(value, -1.0, 1.0));
};
/**
* Computes Math.asin(value), but first clamps value
to the range [-1.0, 1.0]
* so that the function will never return NaN.
*
* @param {Number} value The value for which to compute asin.
* @returns {Number} The asin of the value if the value is in the range [-1.0, 1.0], or the asin of -1.0 or 1.0,
* whichever is closer, if the value is outside the range.
*/
CesiumMath.asinClamped = function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
return Math.asin(CesiumMath.clamp(value, -1.0, 1.0));
};
/**
* Finds the chord length between two points given the circle's radius and the angle between the points.
*
* @param {Number} angle The angle between the two points.
* @param {Number} radius The radius of the circle.
* @returns {Number} The chord length.
*/
CesiumMath.chordLength = function(angle, radius) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
if (!defined(radius)) {
throw new DeveloperError('radius is required.');
}
return 2.0 * radius * Math.sin(angle * 0.5);
};
/**
* Finds the logarithm of a number to a base.
*
* @param {Number} number The number.
* @param {Number} base The base.
* @returns {Number} The result.
*/
CesiumMath.logBase = function(number, base) {
if (!defined(number)) {
throw new DeveloperError('number is required.');
}
if (!defined(base)) {
throw new DeveloperError('base is required.');
}
return Math.log(number) / Math.log(base);
};
/**
* @private
*/
CesiumMath.fog = function(distanceToCamera, density) {
var scalar = distanceToCamera * density;
return 1.0 - Math.exp(-(scalar * scalar));
};
return CesiumMath;
});
/*global define*/
define('Core/Cartesian3',[
'./Check',
'./defaultValue',
'./defined',
'./DeveloperError',
'./freezeObject',
'./Math'
], function(
Check,
defaultValue,
defined,
DeveloperError,
freezeObject,
CesiumMath) {
'use strict';
/**
* A 3D Cartesian point.
* @alias Cartesian3
* @constructor
*
* @param {Number} [x=0.0] The X component.
* @param {Number} [y=0.0] The Y component.
* @param {Number} [z=0.0] The Z component.
*
* @see Cartesian2
* @see Cartesian4
* @see Packable
*/
function Cartesian3(x, y, z) {
/**
* The X component.
* @type {Number}
* @default 0.0
*/
this.x = defaultValue(x, 0.0);
/**
* The Y component.
* @type {Number}
* @default 0.0
*/
this.y = defaultValue(y, 0.0);
/**
* The Z component.
* @type {Number}
* @default 0.0
*/
this.z = defaultValue(z, 0.0);
}
/**
* Converts the provided Spherical into Cartesian3 coordinates.
*
* @param {Spherical} spherical The Spherical to be converted to Cartesian3.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*/
Cartesian3.fromSpherical = function(spherical, result) {
Check.typeOf.object(spherical, 'spherical');
if (!defined(result)) {
result = new Cartesian3();
}
var clock = spherical.clock;
var cone = spherical.cone;
var magnitude = defaultValue(spherical.magnitude, 1.0);
var radial = magnitude * Math.sin(cone);
result.x = radial * Math.cos(clock);
result.y = radial * Math.sin(clock);
result.z = magnitude * Math.cos(cone);
return result;
};
/**
* Creates a Cartesian3 instance from x, y and z coordinates.
*
* @param {Number} x The x coordinate.
* @param {Number} y The y coordinate.
* @param {Number} z The z coordinate.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*/
Cartesian3.fromElements = function(x, y, z, result) {
if (!defined(result)) {
return new Cartesian3(x, y, z);
}
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Duplicates a Cartesian3 instance.
*
* @param {Cartesian3} cartesian The Cartesian to duplicate.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. (Returns undefined if cartesian is undefined)
*/
Cartesian3.clone = function(cartesian, result) {
if (!defined(cartesian)) {
return undefined;
}
if (!defined(result)) {
return new Cartesian3(cartesian.x, cartesian.y, cartesian.z);
}
result.x = cartesian.x;
result.y = cartesian.y;
result.z = cartesian.z;
return result;
};
/**
* Creates a Cartesian3 instance from an existing Cartesian4. This simply takes the
* x, y, and z properties of the Cartesian4 and drops w.
* @function
*
* @param {Cartesian4} cartesian The Cartesian4 instance to create a Cartesian3 instance from.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*/
Cartesian3.fromCartesian4 = Cartesian3.clone;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Cartesian3.packedLength = 3;
/**
* Stores the provided instance into the provided array.
*
* @param {Cartesian3} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Cartesian3.pack = function(value, array, startingIndex) {
Check.typeOf.object(value, 'value');
Check.defined(array, 'array');
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.x;
array[startingIndex++] = value.y;
array[startingIndex] = value.z;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Cartesian3} [result] The object into which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*/
Cartesian3.unpack = function(array, startingIndex, result) {
Check.defined(array, 'array');
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Cartesian3();
}
result.x = array[startingIndex++];
result.y = array[startingIndex++];
result.z = array[startingIndex];
return result;
};
/**
* Flattens an array of Cartesian3s into an array of components.
*
* @param {Cartesian3[]} array The array of cartesians to pack.
* @param {Number[]} result The array onto which to store the result.
* @returns {Number[]} The packed array.
*/
Cartesian3.packArray = function(array, result) {
Check.defined(array, 'array');
var length = array.length;
if (!defined(result)) {
result = new Array(length * 3);
} else {
result.length = length * 3;
}
for (var i = 0; i < length; ++i) {
Cartesian3.pack(array[i], result, i * 3);
}
return result;
};
/**
* Unpacks an array of cartesian components into an array of Cartesian3s.
*
* @param {Number[]} array The array of components to unpack.
* @param {Cartesian3[]} result The array onto which to store the result.
* @returns {Cartesian3[]} The unpacked array.
*/
Cartesian3.unpackArray = function(array, result) {
Check.defined(array, 'array');
Check.numeric.minimum(array.length, 3);
if (array.length % 3 !== 0) {
throw new DeveloperError('array length must be a multiple of 3.');
}
var length = array.length;
if (!defined(result)) {
result = new Array(length / 3);
} else {
result.length = length / 3;
}
for (var i = 0; i < length; i += 3) {
var index = i / 3;
result[index] = Cartesian3.unpack(array, i, result[index]);
}
return result;
};
/**
* Creates a Cartesian3 from three consecutive elements in an array.
* @function
*
* @param {Number[]} array The array whose three consecutive elements correspond to the x, y, and z components, respectively.
* @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to the x component.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*
* @example
* // Create a Cartesian3 with (1.0, 2.0, 3.0)
* var v = [1.0, 2.0, 3.0];
* var p = Cesium.Cartesian3.fromArray(v);
*
* // Create a Cartesian3 with (1.0, 2.0, 3.0) using an offset into an array
* var v2 = [0.0, 0.0, 1.0, 2.0, 3.0];
* var p2 = Cesium.Cartesian3.fromArray(v2, 2);
*/
Cartesian3.fromArray = Cartesian3.unpack;
/**
* Computes the value of the maximum component for the supplied Cartesian.
*
* @param {Cartesian3} cartesian The cartesian to use.
* @returns {Number} The value of the maximum component.
*/
Cartesian3.maximumComponent = function(cartesian) {
Check.typeOf.object(cartesian, 'cartesian');
return Math.max(cartesian.x, cartesian.y, cartesian.z);
};
/**
* Computes the value of the minimum component for the supplied Cartesian.
*
* @param {Cartesian3} cartesian The cartesian to use.
* @returns {Number} The value of the minimum component.
*/
Cartesian3.minimumComponent = function(cartesian) {
Check.typeOf.object(cartesian, 'cartesian');
return Math.min(cartesian.x, cartesian.y, cartesian.z);
};
/**
* Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
*
* @param {Cartesian3} first A cartesian to compare.
* @param {Cartesian3} second A cartesian to compare.
* @param {Cartesian3} result The object into which to store the result.
* @returns {Cartesian3} A cartesian with the minimum components.
*/
Cartesian3.minimumByComponent = function(first, second, result) {
Check.typeOf.object(first, 'first');
Check.typeOf.object(second, 'second');
Check.typeOf.object(result, 'result');
result.x = Math.min(first.x, second.x);
result.y = Math.min(first.y, second.y);
result.z = Math.min(first.z, second.z);
return result;
};
/**
* Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
*
* @param {Cartesian3} first A cartesian to compare.
* @param {Cartesian3} second A cartesian to compare.
* @param {Cartesian3} result The object into which to store the result.
* @returns {Cartesian3} A cartesian with the maximum components.
*/
Cartesian3.maximumByComponent = function(first, second, result) {
Check.typeOf.object(first, 'first');
Check.typeOf.object(second, 'second');
Check.typeOf.object(result, 'result');
result.x = Math.max(first.x, second.x);
result.y = Math.max(first.y, second.y);
result.z = Math.max(first.z, second.z);
return result;
};
/**
* Computes the provided Cartesian's squared magnitude.
*
* @param {Cartesian3} cartesian The Cartesian instance whose squared magnitude is to be computed.
* @returns {Number} The squared magnitude.
*/
Cartesian3.magnitudeSquared = function(cartesian) {
Check.typeOf.object(cartesian, 'cartesian');
return cartesian.x * cartesian.x + cartesian.y * cartesian.y + cartesian.z * cartesian.z;
};
/**
* Computes the Cartesian's magnitude (length).
*
* @param {Cartesian3} cartesian The Cartesian instance whose magnitude is to be computed.
* @returns {Number} The magnitude.
*/
Cartesian3.magnitude = function(cartesian) {
return Math.sqrt(Cartesian3.magnitudeSquared(cartesian));
};
var distanceScratch = new Cartesian3();
/**
* Computes the distance between two points.
*
* @param {Cartesian3} left The first point to compute the distance from.
* @param {Cartesian3} right The second point to compute the distance to.
* @returns {Number} The distance between two points.
*
* @example
* // Returns 1.0
* var d = Cesium.Cartesian3.distance(new Cesium.Cartesian3(1.0, 0.0, 0.0), new Cesium.Cartesian3(2.0, 0.0, 0.0));
*/
Cartesian3.distance = function(left, right) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Cartesian3.subtract(left, right, distanceScratch);
return Cartesian3.magnitude(distanceScratch);
};
/**
* Computes the squared distance between two points. Comparing squared distances
* using this function is more efficient than comparing distances using {@link Cartesian3#distance}.
*
* @param {Cartesian3} left The first point to compute the distance from.
* @param {Cartesian3} right The second point to compute the distance to.
* @returns {Number} The distance between two points.
*
* @example
* // Returns 4.0, not 2.0
* var d = Cesium.Cartesian3.distanceSquared(new Cesium.Cartesian3(1.0, 0.0, 0.0), new Cesium.Cartesian3(3.0, 0.0, 0.0));
*/
Cartesian3.distanceSquared = function(left, right) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Cartesian3.subtract(left, right, distanceScratch);
return Cartesian3.magnitudeSquared(distanceScratch);
};
/**
* Computes the normalized form of the supplied Cartesian.
*
* @param {Cartesian3} cartesian The Cartesian to be normalized.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.normalize = function(cartesian, result) {
Check.typeOf.object(cartesian, 'cartesian');
Check.typeOf.object(result, 'result');
var magnitude = Cartesian3.magnitude(cartesian);
result.x = cartesian.x / magnitude;
result.y = cartesian.y / magnitude;
result.z = cartesian.z / magnitude;
if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z)) {
throw new DeveloperError('normalized result is not a number');
}
return result;
};
/**
* Computes the dot (scalar) product of two Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @returns {Number} The dot product.
*/
Cartesian3.dot = function(left, right) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
return left.x * right.x + left.y * right.y + left.z * right.z;
};
/**
* Computes the componentwise product of two Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.multiplyComponents = function(left, right, result) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Check.typeOf.object(result, 'result');
result.x = left.x * right.x;
result.y = left.y * right.y;
result.z = left.z * right.z;
return result;
};
/**
* Computes the componentwise quotient of two Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.divideComponents = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x / right.x;
result.y = left.y / right.y;
result.z = left.z / right.z;
return result;
};
/**
* Computes the componentwise sum of two Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.add = function(left, right, result) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Check.typeOf.object(result, 'result');
result.x = left.x + right.x;
result.y = left.y + right.y;
result.z = left.z + right.z;
return result;
};
/**
* Computes the componentwise difference of two Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.subtract = function(left, right, result) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Check.typeOf.object(result, 'result');
result.x = left.x - right.x;
result.y = left.y - right.y;
result.z = left.z - right.z;
return result;
};
/**
* Multiplies the provided Cartesian componentwise by the provided scalar.
*
* @param {Cartesian3} cartesian The Cartesian to be scaled.
* @param {Number} scalar The scalar to multiply with.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.multiplyByScalar = function(cartesian, scalar, result) {
Check.typeOf.object(cartesian, 'cartesian');
Check.typeOf.number(scalar, 'scalar');
Check.typeOf.object(result, 'result');
result.x = cartesian.x * scalar;
result.y = cartesian.y * scalar;
result.z = cartesian.z * scalar;
return result;
};
/**
* Divides the provided Cartesian componentwise by the provided scalar.
*
* @param {Cartesian3} cartesian The Cartesian to be divided.
* @param {Number} scalar The scalar to divide by.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.divideByScalar = function(cartesian, scalar, result) {
Check.typeOf.object(cartesian, 'cartesian');
Check.typeOf.number(scalar, 'scalar');
Check.typeOf.object(result, 'result');
result.x = cartesian.x / scalar;
result.y = cartesian.y / scalar;
result.z = cartesian.z / scalar;
return result;
};
/**
* Negates the provided Cartesian.
*
* @param {Cartesian3} cartesian The Cartesian to be negated.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.negate = function(cartesian, result) {
Check.typeOf.object(cartesian, 'cartesian');
Check.typeOf.object(result, 'result');
result.x = -cartesian.x;
result.y = -cartesian.y;
result.z = -cartesian.z;
return result;
};
/**
* Computes the absolute value of the provided Cartesian.
*
* @param {Cartesian3} cartesian The Cartesian whose absolute value is to be computed.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.abs = function(cartesian, result) {
Check.typeOf.object(cartesian, 'cartesian');
Check.typeOf.object(result, 'result');
result.x = Math.abs(cartesian.x);
result.y = Math.abs(cartesian.y);
result.z = Math.abs(cartesian.z);
return result;
};
var lerpScratch = new Cartesian3();
/**
* Computes the linear interpolation or extrapolation at t using the provided cartesians.
*
* @param {Cartesian3} start The value corresponding to t at 0.0.
* @param {Cartesian3} end The value corresponding to t at 1.0.
* @param {Number} t The point along t at which to interpolate.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Cartesian3.lerp = function(start, end, t, result) {
Check.typeOf.object(start, 'start');
Check.typeOf.object(end, 'end');
Check.typeOf.number(t, 't');
Check.typeOf.object(result, 'result');
Cartesian3.multiplyByScalar(end, t, lerpScratch);
result = Cartesian3.multiplyByScalar(start, 1.0 - t, result);
return Cartesian3.add(lerpScratch, result, result);
};
var angleBetweenScratch = new Cartesian3();
var angleBetweenScratch2 = new Cartesian3();
/**
* Returns the angle, in radians, between the provided Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @returns {Number} The angle between the Cartesians.
*/
Cartesian3.angleBetween = function(left, right) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Cartesian3.normalize(left, angleBetweenScratch);
Cartesian3.normalize(right, angleBetweenScratch2);
var cosine = Cartesian3.dot(angleBetweenScratch, angleBetweenScratch2);
var sine = Cartesian3.magnitude(Cartesian3.cross(angleBetweenScratch, angleBetweenScratch2, angleBetweenScratch));
return Math.atan2(sine, cosine);
};
var mostOrthogonalAxisScratch = new Cartesian3();
/**
* Returns the axis that is most orthogonal to the provided Cartesian.
*
* @param {Cartesian3} cartesian The Cartesian on which to find the most orthogonal axis.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The most orthogonal axis.
*/
Cartesian3.mostOrthogonalAxis = function(cartesian, result) {
Check.typeOf.object(cartesian, 'cartesian');
Check.typeOf.object(result, 'result');
var f = Cartesian3.normalize(cartesian, mostOrthogonalAxisScratch);
Cartesian3.abs(f, f);
if (f.x <= f.y) {
if (f.x <= f.z) {
result = Cartesian3.clone(Cartesian3.UNIT_X, result);
} else {
result = Cartesian3.clone(Cartesian3.UNIT_Z, result);
}
} else {
if (f.y <= f.z) {
result = Cartesian3.clone(Cartesian3.UNIT_Y, result);
} else {
result = Cartesian3.clone(Cartesian3.UNIT_Z, result);
}
}
return result;
};
/**
* Compares the provided Cartesians componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartesian3} [left] The first Cartesian.
* @param {Cartesian3} [right] The second Cartesian.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Cartesian3.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.x === right.x) &&
(left.y === right.y) &&
(left.z === right.z));
};
/**
* @private
*/
Cartesian3.equalsArray = function(cartesian, array, offset) {
return cartesian.x === array[offset] &&
cartesian.y === array[offset + 1] &&
cartesian.z === array[offset + 2];
};
/**
* Compares the provided Cartesians componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {Cartesian3} [left] The first Cartesian.
* @param {Cartesian3} [right] The second Cartesian.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Cartesian3.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
return (left === right) ||
(defined(left) &&
defined(right) &&
CesiumMath.equalsEpsilon(left.x, right.x, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.y, right.y, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.z, right.z, relativeEpsilon, absoluteEpsilon));
};
/**
* Computes the cross (outer) product of two Cartesians.
*
* @param {Cartesian3} left The first Cartesian.
* @param {Cartesian3} right The second Cartesian.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The cross product.
*/
Cartesian3.cross = function(left, right, result) {
Check.typeOf.object(left, 'left');
Check.typeOf.object(right, 'right');
Check.typeOf.object(result, 'result');
var leftX = left.x;
var leftY = left.y;
var leftZ = left.z;
var rightX = right.x;
var rightY = right.y;
var rightZ = right.z;
var x = leftY * rightZ - leftZ * rightY;
var y = leftZ * rightX - leftX * rightZ;
var z = leftX * rightY - leftY * rightX;
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Returns a Cartesian3 position from longitude and latitude values given in degrees.
*
* @param {Number} longitude The longitude, in degrees
* @param {Number} latitude The latitude, in degrees
* @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The position
*
* @example
* var position = Cesium.Cartesian3.fromDegrees(-115.0, 37.0);
*/
Cartesian3.fromDegrees = function(longitude, latitude, height, ellipsoid, result) {
Check.typeOf.number(longitude, 'longitude');
Check.typeOf.number(latitude, 'latitude');
longitude = CesiumMath.toRadians(longitude);
latitude = CesiumMath.toRadians(latitude);
return Cartesian3.fromRadians(longitude, latitude, height, ellipsoid, result);
};
var scratchN = new Cartesian3();
var scratchK = new Cartesian3();
var wgs84RadiiSquared = new Cartesian3(6378137.0 * 6378137.0, 6378137.0 * 6378137.0, 6356752.3142451793 * 6356752.3142451793);
/**
* Returns a Cartesian3 position from longitude and latitude values given in radians.
*
* @param {Number} longitude The longitude, in radians
* @param {Number} latitude The latitude, in radians
* @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The position
*
* @example
* var position = Cesium.Cartesian3.fromRadians(-2.007, 0.645);
*/
Cartesian3.fromRadians = function(longitude, latitude, height, ellipsoid, result) {
Check.typeOf.number(longitude, 'longitude');
Check.typeOf.number(latitude, 'latitude');
height = defaultValue(height, 0.0);
var radiiSquared = defined(ellipsoid) ? ellipsoid.radiiSquared : wgs84RadiiSquared;
var cosLatitude = Math.cos(latitude);
scratchN.x = cosLatitude * Math.cos(longitude);
scratchN.y = cosLatitude * Math.sin(longitude);
scratchN.z = Math.sin(latitude);
scratchN = Cartesian3.normalize(scratchN, scratchN);
Cartesian3.multiplyComponents(radiiSquared, scratchN, scratchK);
var gamma = Math.sqrt(Cartesian3.dot(scratchN, scratchK));
scratchK = Cartesian3.divideByScalar(scratchK, gamma, scratchK);
scratchN = Cartesian3.multiplyByScalar(scratchN, height, scratchN);
if (!defined(result)) {
result = new Cartesian3();
}
return Cartesian3.add(scratchK, scratchN, result);
};
/**
* Returns an array of Cartesian3 positions given an array of longitude and latitude values given in degrees.
*
* @param {Number[]} coordinates A list of longitude and latitude values. Values alternate [longitude, latitude, longitude, latitude...].
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the coordinates lie.
* @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
* @returns {Cartesian3[]} The array of positions.
*
* @example
* var positions = Cesium.Cartesian3.fromDegreesArray([-115.0, 37.0, -107.0, 33.0]);
*/
Cartesian3.fromDegreesArray = function(coordinates, ellipsoid, result) {
Check.defined(coordinates, 'coordinates');
if (coordinates.length < 2 || coordinates.length % 2 !== 0) {
throw new DeveloperError('the number of coordinates must be a multiple of 2 and at least 2');
}
var length = coordinates.length;
if (!defined(result)) {
result = new Array(length / 2);
} else {
result.length = length / 2;
}
for (var i = 0; i < length; i += 2) {
var longitude = coordinates[i];
var latitude = coordinates[i + 1];
var index = i / 2;
result[index] = Cartesian3.fromDegrees(longitude, latitude, 0, ellipsoid, result[index]);
}
return result;
};
/**
* Returns an array of Cartesian3 positions given an array of longitude and latitude values given in radians.
*
* @param {Number[]} coordinates A list of longitude and latitude values. Values alternate [longitude, latitude, longitude, latitude...].
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the coordinates lie.
* @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
* @returns {Cartesian3[]} The array of positions.
*
* @example
* var positions = Cesium.Cartesian3.fromRadiansArray([-2.007, 0.645, -1.867, .575]);
*/
Cartesian3.fromRadiansArray = function(coordinates, ellipsoid, result) {
Check.defined(coordinates, 'coordinates');
if (coordinates.length < 2 || coordinates.length % 2 !== 0) {
throw new DeveloperError('the number of coordinates must be a multiple of 2 and at least 2');
}
var length = coordinates.length;
if (!defined(result)) {
result = new Array(length / 2);
} else {
result.length = length / 2;
}
for (var i = 0; i < length; i += 2) {
var longitude = coordinates[i];
var latitude = coordinates[i + 1];
var index = i / 2;
result[index] = Cartesian3.fromRadians(longitude, latitude, 0, ellipsoid, result[index]);
}
return result;
};
/**
* Returns an array of Cartesian3 positions given an array of longitude, latitude and height values where longitude and latitude are given in degrees.
*
* @param {Number[]} coordinates A list of longitude, latitude and height values. Values alternate [longitude, latitude, height, longitude, latitude, height...].
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
* @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
* @returns {Cartesian3[]} The array of positions.
*
* @example
* var positions = Cesium.Cartesian3.fromDegreesArrayHeights([-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]);
*/
Cartesian3.fromDegreesArrayHeights = function(coordinates, ellipsoid, result) {
Check.defined(coordinates, 'coordinates');
if (coordinates.length < 3 || coordinates.length % 3 !== 0) {
throw new DeveloperError('the number of coordinates must be a multiple of 3 and at least 3');
}
var length = coordinates.length;
if (!defined(result)) {
result = new Array(length / 3);
} else {
result.length = length / 3;
}
for (var i = 0; i < length; i += 3) {
var longitude = coordinates[i];
var latitude = coordinates[i + 1];
var height = coordinates[i + 2];
var index = i / 3;
result[index] = Cartesian3.fromDegrees(longitude, latitude, height, ellipsoid, result[index]);
}
return result;
};
/**
* Returns an array of Cartesian3 positions given an array of longitude, latitude and height values where longitude and latitude are given in radians.
*
* @param {Number[]} coordinates A list of longitude, latitude and height values. Values alternate [longitude, latitude, height, longitude, latitude, height...].
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
* @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
* @returns {Cartesian3[]} The array of positions.
*
* @example
* var positions = Cesium.Cartesian3.fromRadiansArrayHeights([-2.007, 0.645, 100000.0, -1.867, .575, 150000.0]);
*/
Cartesian3.fromRadiansArrayHeights = function(coordinates, ellipsoid, result) {
Check.defined(coordinates, 'coordinates');
if (coordinates.length < 3 || coordinates.length % 3 !== 0) {
throw new DeveloperError('the number of coordinates must be a multiple of 3 and at least 3');
}
var length = coordinates.length;
if (!defined(result)) {
result = new Array(length / 3);
} else {
result.length = length / 3;
}
for (var i = 0; i < length; i += 3) {
var longitude = coordinates[i];
var latitude = coordinates[i + 1];
var height = coordinates[i + 2];
var index = i / 3;
result[index] = Cartesian3.fromRadians(longitude, latitude, height, ellipsoid, result[index]);
}
return result;
};
/**
* An immutable Cartesian3 instance initialized to (0.0, 0.0, 0.0).
*
* @type {Cartesian3}
* @constant
*/
Cartesian3.ZERO = freezeObject(new Cartesian3(0.0, 0.0, 0.0));
/**
* An immutable Cartesian3 instance initialized to (1.0, 0.0, 0.0).
*
* @type {Cartesian3}
* @constant
*/
Cartesian3.UNIT_X = freezeObject(new Cartesian3(1.0, 0.0, 0.0));
/**
* An immutable Cartesian3 instance initialized to (0.0, 1.0, 0.0).
*
* @type {Cartesian3}
* @constant
*/
Cartesian3.UNIT_Y = freezeObject(new Cartesian3(0.0, 1.0, 0.0));
/**
* An immutable Cartesian3 instance initialized to (0.0, 0.0, 1.0).
*
* @type {Cartesian3}
* @constant
*/
Cartesian3.UNIT_Z = freezeObject(new Cartesian3(0.0, 0.0, 1.0));
/**
* Duplicates this Cartesian3 instance.
*
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*/
Cartesian3.prototype.clone = function(result) {
return Cartesian3.clone(this, result);
};
/**
* Compares this Cartesian against the provided Cartesian componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartesian3} [right] The right hand side Cartesian.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Cartesian3.prototype.equals = function(right) {
return Cartesian3.equals(this, right);
};
/**
* Compares this Cartesian against the provided Cartesian componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {Cartesian3} [right] The right hand side Cartesian.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
Cartesian3.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
return Cartesian3.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
};
/**
* Creates a string representing this Cartesian in the format '(x, y, z)'.
*
* @returns {String} A string representing this Cartesian in the format '(x, y, z)'.
*/
Cartesian3.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ', ' + this.z + ')';
};
return Cartesian3;
});
/*global define*/
define('Core/scaleToGeodeticSurface',[
'./Cartesian3',
'./defined',
'./DeveloperError',
'./Math'
], function(
Cartesian3,
defined,
DeveloperError,
CesiumMath) {
'use strict';
var scaleToGeodeticSurfaceIntersection = new Cartesian3();
var scaleToGeodeticSurfaceGradient = new Cartesian3();
/**
* Scales the provided Cartesian position along the geodetic surface normal
* so that it is on the surface of this ellipsoid. If the position is
* at the center of the ellipsoid, this function returns undefined.
*
* @param {Cartesian3} cartesian The Cartesian position to scale.
* @param {Cartesian3} oneOverRadii One over radii of the ellipsoid.
* @param {Cartesian3} oneOverRadiiSquared One over radii squared of the ellipsoid.
* @param {Number} centerToleranceSquared Tolerance for closeness to the center.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter, a new Cartesian3 instance if none was provided, or undefined if the position is at the center.
*
* @exports scaleToGeodeticSurface
*
* @private
*/
function scaleToGeodeticSurface(cartesian, oneOverRadii, oneOverRadiiSquared, centerToleranceSquared, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
if (!defined(oneOverRadii)) {
throw new DeveloperError('oneOverRadii is required.');
}
if (!defined(oneOverRadiiSquared)) {
throw new DeveloperError('oneOverRadiiSquared is required.');
}
if (!defined(centerToleranceSquared)) {
throw new DeveloperError('centerToleranceSquared is required.');
}
var positionX = cartesian.x;
var positionY = cartesian.y;
var positionZ = cartesian.z;
var oneOverRadiiX = oneOverRadii.x;
var oneOverRadiiY = oneOverRadii.y;
var oneOverRadiiZ = oneOverRadii.z;
var x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX;
var y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY;
var z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ;
// Compute the squared ellipsoid norm.
var squaredNorm = x2 + y2 + z2;
var ratio = Math.sqrt(1.0 / squaredNorm);
// As an initial approximation, assume that the radial intersection is the projection point.
var intersection = Cartesian3.multiplyByScalar(cartesian, ratio, scaleToGeodeticSurfaceIntersection);
// If the position is near the center, the iteration will not converge.
if (squaredNorm < centerToleranceSquared) {
return !isFinite(ratio) ? undefined : Cartesian3.clone(intersection, result);
}
var oneOverRadiiSquaredX = oneOverRadiiSquared.x;
var oneOverRadiiSquaredY = oneOverRadiiSquared.y;
var oneOverRadiiSquaredZ = oneOverRadiiSquared.z;
// Use the gradient at the intersection point in place of the true unit normal.
// The difference in magnitude will be absorbed in the multiplier.
var gradient = scaleToGeodeticSurfaceGradient;
gradient.x = intersection.x * oneOverRadiiSquaredX * 2.0;
gradient.y = intersection.y * oneOverRadiiSquaredY * 2.0;
gradient.z = intersection.z * oneOverRadiiSquaredZ * 2.0;
// Compute the initial guess at the normal vector multiplier, lambda.
var lambda = (1.0 - ratio) * Cartesian3.magnitude(cartesian) / (0.5 * Cartesian3.magnitude(gradient));
var correction = 0.0;
var func;
var denominator;
var xMultiplier;
var yMultiplier;
var zMultiplier;
var xMultiplier2;
var yMultiplier2;
var zMultiplier2;
var xMultiplier3;
var yMultiplier3;
var zMultiplier3;
do {
lambda -= correction;
xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX);
yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY);
zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ);
xMultiplier2 = xMultiplier * xMultiplier;
yMultiplier2 = yMultiplier * yMultiplier;
zMultiplier2 = zMultiplier * zMultiplier;
xMultiplier3 = xMultiplier2 * xMultiplier;
yMultiplier3 = yMultiplier2 * yMultiplier;
zMultiplier3 = zMultiplier2 * zMultiplier;
func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0;
// "denominator" here refers to the use of this expression in the velocity and acceleration
// computations in the sections to follow.
denominator = x2 * xMultiplier3 * oneOverRadiiSquaredX + y2 * yMultiplier3 * oneOverRadiiSquaredY + z2 * zMultiplier3 * oneOverRadiiSquaredZ;
var derivative = -2.0 * denominator;
correction = func / derivative;
} while (Math.abs(func) > CesiumMath.EPSILON12);
if (!defined(result)) {
return new Cartesian3(positionX * xMultiplier, positionY * yMultiplier, positionZ * zMultiplier);
}
result.x = positionX * xMultiplier;
result.y = positionY * yMultiplier;
result.z = positionZ * zMultiplier;
return result;
}
return scaleToGeodeticSurface;
});
/*global define*/
define('Core/Cartographic',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./freezeObject',
'./Math',
'./scaleToGeodeticSurface'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
freezeObject,
CesiumMath,
scaleToGeodeticSurface) {
'use strict';
/**
* A position defined by longitude, latitude, and height.
* @alias Cartographic
* @constructor
*
* @param {Number} [longitude=0.0] The longitude, in radians.
* @param {Number} [latitude=0.0] The latitude, in radians.
* @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
*
* @see Ellipsoid
*/
function Cartographic(longitude, latitude, height) {
/**
* The longitude, in radians.
* @type {Number}
* @default 0.0
*/
this.longitude = defaultValue(longitude, 0.0);
/**
* The latitude, in radians.
* @type {Number}
* @default 0.0
*/
this.latitude = defaultValue(latitude, 0.0);
/**
* The height, in meters, above the ellipsoid.
* @type {Number}
* @default 0.0
*/
this.height = defaultValue(height, 0.0);
}
/**
* Creates a new Cartographic instance from longitude and latitude
* specified in radians.
*
* @param {Number} longitude The longitude, in radians.
* @param {Number} latitude The latitude, in radians.
* @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
*/
Cartographic.fromRadians = function(longitude, latitude, height, result) {
if (!defined(longitude)) {
throw new DeveloperError('longitude is required.');
}
if (!defined(latitude)) {
throw new DeveloperError('latitude is required.');
}
height = defaultValue(height, 0.0);
if (!defined(result)) {
return new Cartographic(longitude, latitude, height);
}
result.longitude = longitude;
result.latitude = latitude;
result.height = height;
return result;
};
/**
* Creates a new Cartographic instance from longitude and latitude
* specified in degrees. The values in the resulting object will
* be in radians.
*
* @param {Number} longitude The longitude, in degrees.
* @param {Number} latitude The latitude, in degrees.
* @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
*/
Cartographic.fromDegrees = function(longitude, latitude, height, result) {
if (!defined(longitude)) {
throw new DeveloperError('longitude is required.');
}
if (!defined(latitude)) {
throw new DeveloperError('latitude is required.');
}
longitude = CesiumMath.toRadians(longitude);
latitude = CesiumMath.toRadians(latitude);
return Cartographic.fromRadians(longitude, latitude, height, result);
};
var cartesianToCartographicN = new Cartesian3();
var cartesianToCartographicP = new Cartesian3();
var cartesianToCartographicH = new Cartesian3();
var wgs84OneOverRadii = new Cartesian3(1.0 / 6378137.0, 1.0 / 6378137.0, 1.0 / 6356752.3142451793);
var wgs84OneOverRadiiSquared = new Cartesian3(1.0 / (6378137.0 * 6378137.0), 1.0 / (6378137.0 * 6378137.0), 1.0 / (6356752.3142451793 * 6356752.3142451793));
var wgs84CenterToleranceSquared = CesiumMath.EPSILON1;
/**
* Creates a new Cartographic instance from a Cartesian position. The values in the
* resulting object will be in radians.
*
* @param {Cartesian3} cartesian The Cartesian position to convert to cartographic representation.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter, new Cartographic instance if none was provided, or undefined if the cartesian is at the center of the ellipsoid.
*/
Cartographic.fromCartesian = function(cartesian, ellipsoid, result) {
var oneOverRadii = defined(ellipsoid) ? ellipsoid.oneOverRadii : wgs84OneOverRadii;
var oneOverRadiiSquared = defined(ellipsoid) ? ellipsoid.oneOverRadiiSquared : wgs84OneOverRadiiSquared;
var centerToleranceSquared = defined(ellipsoid) ? ellipsoid._centerToleranceSquared : wgs84CenterToleranceSquared;
//`cartesian is required.` is thrown from scaleToGeodeticSurface
var p = scaleToGeodeticSurface(cartesian, oneOverRadii, oneOverRadiiSquared, centerToleranceSquared, cartesianToCartographicP);
if (!defined(p)) {
return undefined;
}
var n = Cartesian3.multiplyComponents(p, oneOverRadiiSquared, cartesianToCartographicN);
n = Cartesian3.normalize(n, n);
var h = Cartesian3.subtract(cartesian, p, cartesianToCartographicH);
var longitude = Math.atan2(n.y, n.x);
var latitude = Math.asin(n.z);
var height = CesiumMath.sign(Cartesian3.dot(h, cartesian)) * Cartesian3.magnitude(h);
if (!defined(result)) {
return new Cartographic(longitude, latitude, height);
}
result.longitude = longitude;
result.latitude = latitude;
result.height = height;
return result;
};
/**
* Duplicates a Cartographic instance.
*
* @param {Cartographic} cartographic The cartographic to duplicate.
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided. (Returns undefined if cartographic is undefined)
*/
Cartographic.clone = function(cartographic, result) {
if (!defined(cartographic)) {
return undefined;
}
if (!defined(result)) {
return new Cartographic(cartographic.longitude, cartographic.latitude, cartographic.height);
}
result.longitude = cartographic.longitude;
result.latitude = cartographic.latitude;
result.height = cartographic.height;
return result;
};
/**
* Compares the provided cartographics componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartographic} [left] The first cartographic.
* @param {Cartographic} [right] The second cartographic.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Cartographic.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.longitude === right.longitude) &&
(left.latitude === right.latitude) &&
(left.height === right.height));
};
/**
* Compares the provided cartographics componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Cartographic} [left] The first cartographic.
* @param {Cartographic} [right] The second cartographic.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Cartographic.equalsEpsilon = function(left, right, epsilon) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon is required and must be a number.');
}
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(Math.abs(left.longitude - right.longitude) <= epsilon) &&
(Math.abs(left.latitude - right.latitude) <= epsilon) &&
(Math.abs(left.height - right.height) <= epsilon));
};
/**
* An immutable Cartographic instance initialized to (0.0, 0.0, 0.0).
*
* @type {Cartographic}
* @constant
*/
Cartographic.ZERO = freezeObject(new Cartographic(0.0, 0.0, 0.0));
/**
* Duplicates this instance.
*
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
*/
Cartographic.prototype.clone = function(result) {
return Cartographic.clone(this, result);
};
/**
* Compares the provided against this cartographic componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartographic} [right] The second cartographic.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Cartographic.prototype.equals = function(right) {
return Cartographic.equals(this, right);
};
/**
* Compares the provided against this cartographic componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Cartographic} [right] The second cartographic.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Cartographic.prototype.equalsEpsilon = function(right, epsilon) {
return Cartographic.equalsEpsilon(this, right, epsilon);
};
/**
* Creates a string representing this cartographic in the format '(longitude, latitude, height)'.
*
* @returns {String} A string representing the provided cartographic in the format '(longitude, latitude, height)'.
*/
Cartographic.prototype.toString = function() {
return '(' + this.longitude + ', ' + this.latitude + ', ' + this.height + ')';
};
return Cartographic;
});
/*global define*/
define('Core/Ellipsoid',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./freezeObject',
'./Math',
'./scaleToGeodeticSurface'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
freezeObject,
CesiumMath,
scaleToGeodeticSurface) {
'use strict';
function initialize(ellipsoid, x, y, z) {
x = defaultValue(x, 0.0);
y = defaultValue(y, 0.0);
z = defaultValue(z, 0.0);
if (x < 0.0 || y < 0.0 || z < 0.0) {
throw new DeveloperError('All radii components must be greater than or equal to zero.');
}
ellipsoid._radii = new Cartesian3(x, y, z);
ellipsoid._radiiSquared = new Cartesian3(x * x,
y * y,
z * z);
ellipsoid._radiiToTheFourth = new Cartesian3(x * x * x * x,
y * y * y * y,
z * z * z * z);
ellipsoid._oneOverRadii = new Cartesian3(x === 0.0 ? 0.0 : 1.0 / x,
y === 0.0 ? 0.0 : 1.0 / y,
z === 0.0 ? 0.0 : 1.0 / z);
ellipsoid._oneOverRadiiSquared = new Cartesian3(x === 0.0 ? 0.0 : 1.0 / (x * x),
y === 0.0 ? 0.0 : 1.0 / (y * y),
z === 0.0 ? 0.0 : 1.0 / (z * z));
ellipsoid._minimumRadius = Math.min(x, y, z);
ellipsoid._maximumRadius = Math.max(x, y, z);
ellipsoid._centerToleranceSquared = CesiumMath.EPSILON1;
if (ellipsoid._radiiSquared.z !== 0) {
ellipsoid._sqauredXOverSquaredZ = ellipsoid._radiiSquared.x / ellipsoid._radiiSquared.z;
}
}
/**
* A quadratic surface defined in Cartesian coordinates by the equation
* (x / a)^2 + (y / b)^2 + (z / c)^2 = 1
. Primarily used
* by Cesium to represent the shape of planetary bodies.
*
* Rather than constructing this object directly, one of the provided
* constants is normally used.
* @alias Ellipsoid
* @constructor
*
* @param {Number} [x=0] The radius in the x direction.
* @param {Number} [y=0] The radius in the y direction.
* @param {Number} [z=0] The radius in the z direction.
*
* @exception {DeveloperError} All radii components must be greater than or equal to zero.
*
* @see Ellipsoid.fromCartesian3
* @see Ellipsoid.WGS84
* @see Ellipsoid.UNIT_SPHERE
*/
function Ellipsoid(x, y, z) {
this._radii = undefined;
this._radiiSquared = undefined;
this._radiiToTheFourth = undefined;
this._oneOverRadii = undefined;
this._oneOverRadiiSquared = undefined;
this._minimumRadius = undefined;
this._maximumRadius = undefined;
this._centerToleranceSquared = undefined;
this._sqauredXOverSquaredZ = undefined;
initialize(this, x, y, z);
}
defineProperties(Ellipsoid.prototype, {
/**
* Gets the radii of the ellipsoid.
* @memberof Ellipsoid.prototype
* @type {Cartesian3}
* @readonly
*/
radii : {
get: function() {
return this._radii;
}
},
/**
* Gets the squared radii of the ellipsoid.
* @memberof Ellipsoid.prototype
* @type {Cartesian3}
* @readonly
*/
radiiSquared : {
get : function() {
return this._radiiSquared;
}
},
/**
* Gets the radii of the ellipsoid raise to the fourth power.
* @memberof Ellipsoid.prototype
* @type {Cartesian3}
* @readonly
*/
radiiToTheFourth : {
get : function() {
return this._radiiToTheFourth;
}
},
/**
* Gets one over the radii of the ellipsoid.
* @memberof Ellipsoid.prototype
* @type {Cartesian3}
* @readonly
*/
oneOverRadii : {
get : function() {
return this._oneOverRadii;
}
},
/**
* Gets one over the squared radii of the ellipsoid.
* @memberof Ellipsoid.prototype
* @type {Cartesian3}
* @readonly
*/
oneOverRadiiSquared : {
get : function() {
return this._oneOverRadiiSquared;
}
},
/**
* Gets the minimum radius of the ellipsoid.
* @memberof Ellipsoid.prototype
* @type {Number}
* @readonly
*/
minimumRadius : {
get : function() {
return this._minimumRadius;
}
},
/**
* Gets the maximum radius of the ellipsoid.
* @memberof Ellipsoid.prototype
* @type {Number}
* @readonly
*/
maximumRadius : {
get : function() {
return this._maximumRadius;
}
}
});
/**
* Duplicates an Ellipsoid instance.
*
* @param {Ellipsoid} ellipsoid The ellipsoid to duplicate.
* @param {Ellipsoid} [result] The object onto which to store the result, or undefined if a new
* instance should be created.
* @returns {Ellipsoid} The cloned Ellipsoid. (Returns undefined if ellipsoid is undefined)
*/
Ellipsoid.clone = function(ellipsoid, result) {
if (!defined(ellipsoid)) {
return undefined;
}
var radii = ellipsoid._radii;
if (!defined(result)) {
return new Ellipsoid(radii.x, radii.y, radii.z);
}
Cartesian3.clone(radii, result._radii);
Cartesian3.clone(ellipsoid._radiiSquared, result._radiiSquared);
Cartesian3.clone(ellipsoid._radiiToTheFourth, result._radiiToTheFourth);
Cartesian3.clone(ellipsoid._oneOverRadii, result._oneOverRadii);
Cartesian3.clone(ellipsoid._oneOverRadiiSquared, result._oneOverRadiiSquared);
result._minimumRadius = ellipsoid._minimumRadius;
result._maximumRadius = ellipsoid._maximumRadius;
result._centerToleranceSquared = ellipsoid._centerToleranceSquared;
return result;
};
/**
* Computes an Ellipsoid from a Cartesian specifying the radii in x, y, and z directions.
*
* @param {Cartesian3} [radii=Cartesian3.ZERO] The ellipsoid's radius in the x, y, and z directions.
* @returns {Ellipsoid} A new Ellipsoid instance.
*
* @exception {DeveloperError} All radii components must be greater than or equal to zero.
*
* @see Ellipsoid.WGS84
* @see Ellipsoid.UNIT_SPHERE
*/
Ellipsoid.fromCartesian3 = function(cartesian, result) {
if (!defined(result)) {
result = new Ellipsoid();
}
if (!defined(cartesian)) {
return result;
}
initialize(result, cartesian.x, cartesian.y, cartesian.z);
return result;
};
/**
* An Ellipsoid instance initialized to the WGS84 standard.
*
* @type {Ellipsoid}
* @constant
*/
Ellipsoid.WGS84 = freezeObject(new Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793));
/**
* An Ellipsoid instance initialized to radii of (1.0, 1.0, 1.0).
*
* @type {Ellipsoid}
* @constant
*/
Ellipsoid.UNIT_SPHERE = freezeObject(new Ellipsoid(1.0, 1.0, 1.0));
/**
* An Ellipsoid instance initialized to a sphere with the lunar radius.
*
* @type {Ellipsoid}
* @constant
*/
Ellipsoid.MOON = freezeObject(new Ellipsoid(CesiumMath.LUNAR_RADIUS, CesiumMath.LUNAR_RADIUS, CesiumMath.LUNAR_RADIUS));
/**
* Duplicates an Ellipsoid instance.
*
* @param {Ellipsoid} [result] The object onto which to store the result, or undefined if a new
* instance should be created.
* @returns {Ellipsoid} The cloned Ellipsoid.
*/
Ellipsoid.prototype.clone = function(result) {
return Ellipsoid.clone(this, result);
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Ellipsoid.packedLength = Cartesian3.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {Ellipsoid} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Ellipsoid.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._radii, array, startingIndex);
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Ellipsoid} [result] The object into which to store the result.
* @returns {Ellipsoid} The modified result parameter or a new Ellipsoid instance if one was not provided.
*/
Ellipsoid.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var radii = Cartesian3.unpack(array, startingIndex);
return Ellipsoid.fromCartesian3(radii, result);
};
/**
* Computes the unit vector directed from the center of this ellipsoid toward the provided Cartesian position.
* @function
*
* @param {Cartesian3} cartesian The Cartesian for which to to determine the geocentric normal.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
*/
Ellipsoid.prototype.geocentricSurfaceNormal = Cartesian3.normalize;
/**
* Computes the normal of the plane tangent to the surface of the ellipsoid at the provided position.
*
* @param {Cartographic} cartographic The cartographic position for which to to determine the geodetic normal.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
*/
Ellipsoid.prototype.geodeticSurfaceNormalCartographic = function(cartographic, result) {
if (!defined(cartographic)) {
throw new DeveloperError('cartographic is required.');
}
var longitude = cartographic.longitude;
var latitude = cartographic.latitude;
var cosLatitude = Math.cos(latitude);
var x = cosLatitude * Math.cos(longitude);
var y = cosLatitude * Math.sin(longitude);
var z = Math.sin(latitude);
if (!defined(result)) {
result = new Cartesian3();
}
result.x = x;
result.y = y;
result.z = z;
return Cartesian3.normalize(result, result);
};
/**
* Computes the normal of the plane tangent to the surface of the ellipsoid at the provided position.
*
* @param {Cartesian3} cartesian The Cartesian position for which to to determine the surface normal.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
*/
Ellipsoid.prototype.geodeticSurfaceNormal = function(cartesian, result) {
if (!defined(result)) {
result = new Cartesian3();
}
result = Cartesian3.multiplyComponents(cartesian, this._oneOverRadiiSquared, result);
return Cartesian3.normalize(result, result);
};
var cartographicToCartesianNormal = new Cartesian3();
var cartographicToCartesianK = new Cartesian3();
/**
* Converts the provided cartographic to Cartesian representation.
*
* @param {Cartographic} cartographic The cartographic position.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
*
* @example
* //Create a Cartographic and determine it's Cartesian representation on a WGS84 ellipsoid.
* var position = new Cesium.Cartographic(Cesium.Math.toRadians(21), Cesium.Math.toRadians(78), 5000);
* var cartesianPosition = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
*/
Ellipsoid.prototype.cartographicToCartesian = function(cartographic, result) {
//`cartographic is required` is thrown from geodeticSurfaceNormalCartographic.
var n = cartographicToCartesianNormal;
var k = cartographicToCartesianK;
this.geodeticSurfaceNormalCartographic(cartographic, n);
Cartesian3.multiplyComponents(this._radiiSquared, n, k);
var gamma = Math.sqrt(Cartesian3.dot(n, k));
Cartesian3.divideByScalar(k, gamma, k);
Cartesian3.multiplyByScalar(n, cartographic.height, n);
if (!defined(result)) {
result = new Cartesian3();
}
return Cartesian3.add(k, n, result);
};
/**
* Converts the provided array of cartographics to an array of Cartesians.
*
* @param {Cartographic[]} cartographics An array of cartographic positions.
* @param {Cartesian3[]} [result] The object onto which to store the result.
* @returns {Cartesian3[]} The modified result parameter or a new Array instance if none was provided.
*
* @example
* //Convert an array of Cartographics and determine their Cartesian representation on a WGS84 ellipsoid.
* var positions = [new Cesium.Cartographic(Cesium.Math.toRadians(21), Cesium.Math.toRadians(78), 0),
* new Cesium.Cartographic(Cesium.Math.toRadians(21.321), Cesium.Math.toRadians(78.123), 100),
* new Cesium.Cartographic(Cesium.Math.toRadians(21.645), Cesium.Math.toRadians(78.456), 250)];
* var cartesianPositions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions);
*/
Ellipsoid.prototype.cartographicArrayToCartesianArray = function(cartographics, result) {
if (!defined(cartographics)) {
throw new DeveloperError('cartographics is required.');
}
var length = cartographics.length;
if (!defined(result)) {
result = new Array(length);
} else {
result.length = length;
}
for ( var i = 0; i < length; i++) {
result[i] = this.cartographicToCartesian(cartographics[i], result[i]);
}
return result;
};
var cartesianToCartographicN = new Cartesian3();
var cartesianToCartographicP = new Cartesian3();
var cartesianToCartographicH = new Cartesian3();
/**
* Converts the provided cartesian to cartographic representation.
* The cartesian is undefined at the center of the ellipsoid.
*
* @param {Cartesian3} cartesian The Cartesian position to convert to cartographic representation.
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter, new Cartographic instance if none was provided, or undefined if the cartesian is at the center of the ellipsoid.
*
* @example
* //Create a Cartesian and determine it's Cartographic representation on a WGS84 ellipsoid.
* var position = new Cesium.Cartesian3(17832.12, 83234.52, 952313.73);
* var cartographicPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(position);
*/
Ellipsoid.prototype.cartesianToCartographic = function(cartesian, result) {
//`cartesian is required.` is thrown from scaleToGeodeticSurface
var p = this.scaleToGeodeticSurface(cartesian, cartesianToCartographicP);
if (!defined(p)) {
return undefined;
}
var n = this.geodeticSurfaceNormal(p, cartesianToCartographicN);
var h = Cartesian3.subtract(cartesian, p, cartesianToCartographicH);
var longitude = Math.atan2(n.y, n.x);
var latitude = Math.asin(n.z);
var height = CesiumMath.sign(Cartesian3.dot(h, cartesian)) * Cartesian3.magnitude(h);
if (!defined(result)) {
return new Cartographic(longitude, latitude, height);
}
result.longitude = longitude;
result.latitude = latitude;
result.height = height;
return result;
};
/**
* Converts the provided array of cartesians to an array of cartographics.
*
* @param {Cartesian3[]} cartesians An array of Cartesian positions.
* @param {Cartographic[]} [result] The object onto which to store the result.
* @returns {Cartographic[]} The modified result parameter or a new Array instance if none was provided.
*
* @example
* //Create an array of Cartesians and determine their Cartographic representation on a WGS84 ellipsoid.
* var positions = [new Cesium.Cartesian3(17832.12, 83234.52, 952313.73),
* new Cesium.Cartesian3(17832.13, 83234.53, 952313.73),
* new Cesium.Cartesian3(17832.14, 83234.54, 952313.73)]
* var cartographicPositions = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray(positions);
*/
Ellipsoid.prototype.cartesianArrayToCartographicArray = function(cartesians, result) {
if (!defined(cartesians)) {
throw new DeveloperError('cartesians is required.');
}
var length = cartesians.length;
if (!defined(result)) {
result = new Array(length);
} else {
result.length = length;
}
for ( var i = 0; i < length; ++i) {
result[i] = this.cartesianToCartographic(cartesians[i], result[i]);
}
return result;
};
/**
* Scales the provided Cartesian position along the geodetic surface normal
* so that it is on the surface of this ellipsoid. If the position is
* at the center of the ellipsoid, this function returns undefined.
*
* @param {Cartesian3} cartesian The Cartesian position to scale.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter, a new Cartesian3 instance if none was provided, or undefined if the position is at the center.
*/
Ellipsoid.prototype.scaleToGeodeticSurface = function(cartesian, result) {
return scaleToGeodeticSurface(cartesian, this._oneOverRadii, this._oneOverRadiiSquared, this._centerToleranceSquared, result);
};
/**
* Scales the provided Cartesian position along the geocentric surface normal
* so that it is on the surface of this ellipsoid.
*
* @param {Cartesian3} cartesian The Cartesian position to scale.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
*/
Ellipsoid.prototype.scaleToGeocentricSurface = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
if (!defined(result)) {
result = new Cartesian3();
}
var positionX = cartesian.x;
var positionY = cartesian.y;
var positionZ = cartesian.z;
var oneOverRadiiSquared = this._oneOverRadiiSquared;
var beta = 1.0 / Math.sqrt((positionX * positionX) * oneOverRadiiSquared.x +
(positionY * positionY) * oneOverRadiiSquared.y +
(positionZ * positionZ) * oneOverRadiiSquared.z);
return Cartesian3.multiplyByScalar(cartesian, beta, result);
};
/**
* Transforms a Cartesian X, Y, Z position to the ellipsoid-scaled space by multiplying
* its components by the result of {@link Ellipsoid#oneOverRadii}.
*
* @param {Cartesian3} position The position to transform.
* @param {Cartesian3} [result] The position to which to copy the result, or undefined to create and
* return a new instance.
* @returns {Cartesian3} The position expressed in the scaled space. The returned instance is the
* one passed as the result parameter if it is not undefined, or a new instance of it is.
*/
Ellipsoid.prototype.transformPositionToScaledSpace = function(position, result) {
if (!defined(result)) {
result = new Cartesian3();
}
return Cartesian3.multiplyComponents(position, this._oneOverRadii, result);
};
/**
* Transforms a Cartesian X, Y, Z position from the ellipsoid-scaled space by multiplying
* its components by the result of {@link Ellipsoid#radii}.
*
* @param {Cartesian3} position The position to transform.
* @param {Cartesian3} [result] The position to which to copy the result, or undefined to create and
* return a new instance.
* @returns {Cartesian3} The position expressed in the unscaled space. The returned instance is the
* one passed as the result parameter if it is not undefined, or a new instance of it is.
*/
Ellipsoid.prototype.transformPositionFromScaledSpace = function(position, result) {
if (!defined(result)) {
result = new Cartesian3();
}
return Cartesian3.multiplyComponents(position, this._radii, result);
};
/**
* Compares this Ellipsoid against the provided Ellipsoid componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Ellipsoid} [right] The other Ellipsoid.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Ellipsoid.prototype.equals = function(right) {
return (this === right) ||
(defined(right) &&
Cartesian3.equals(this._radii, right._radii));
};
/**
* Creates a string representing this Ellipsoid in the format '(radii.x, radii.y, radii.z)'.
*
* @returns {String} A string representing this ellipsoid in the format '(radii.x, radii.y, radii.z)'.
*/
Ellipsoid.prototype.toString = function() {
return this._radii.toString();
};
/**
* Computes a point which is the intersection of the surface normal with the z-axis.
*
* @param {Cartesian3} position the position. must be on the surface of the ellipsoid.
* @param {Number} [buffer = 0.0] A buffer to subtract from the ellipsoid size when checking if the point is inside the ellipsoid.
* In earth case, with common earth datums, there is no need for this buffer since the intersection point is always (relatively) very close to the center.
* In WGS84 datum, intersection point is at max z = +-42841.31151331382 (0.673% of z-axis).
* Intersection point could be outside the ellipsoid if the ratio of MajorAxis / AxisOfRotation is bigger than the square root of 2
* @param {Cartesian} [result] The cartesian to which to copy the result, or undefined to create and
* return a new instance.
* @returns {Cartesian | undefined} the intersection point if it's inside the ellipsoid, undefined otherwise
*
* @exception {DeveloperError} position is required.
* @exception {DeveloperError} Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y).
* @exception {DeveloperError} Ellipsoid.radii.z must be greater than 0.
*/
Ellipsoid.prototype.getSurfaceNormalIntersectionWithZAxis = function(position, buffer, result) {
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
if (!CesiumMath.equalsEpsilon(this._radii.x, this._radii.y, CesiumMath.EPSILON15)) {
throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)');
}
if (this._radii.z === 0) {
throw new DeveloperError('Ellipsoid.radii.z must be greater than 0');
}
buffer = defaultValue(buffer, 0.0);
var sqauredXOverSquaredZ = this._sqauredXOverSquaredZ;
if (!defined(result)) {
result = new Cartesian3();
}
result.x = 0.0;
result.y = 0.0;
result.z = position.z * (1 - sqauredXOverSquaredZ);
if (Math.abs(result.z) >= this._radii.z - buffer) {
return undefined;
}
return result;
};
return Ellipsoid;
});
/*global define*/
define('Core/Event',[
'./defined',
'./defineProperties',
'./DeveloperError'
], function(
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* A generic utility class for managing subscribers for a particular event.
* This class is usually instantiated inside of a container class and
* exposed as a property for others to subscribe to.
*
* @alias Event
* @constructor
*
* @example
* MyObject.prototype.myListener = function(arg1, arg2) {
* this.myArg1Copy = arg1;
* this.myArg2Copy = arg2;
* }
*
* var myObjectInstance = new MyObject();
* var evt = new Cesium.Event();
* evt.addEventListener(MyObject.prototype.myListener, myObjectInstance);
* evt.raiseEvent('1', '2');
* evt.removeEventListener(MyObject.prototype.myListener);
*/
function Event() {
this._listeners = [];
this._scopes = [];
this._toRemove = [];
this._insideRaiseEvent = false;
}
defineProperties(Event.prototype, {
/**
* The number of listeners currently subscribed to the event.
* @memberof Event.prototype
* @type {Number}
* @readonly
*/
numberOfListeners : {
get : function() {
return this._listeners.length - this._toRemove.length;
}
}
});
/**
* Registers a callback function to be executed whenever the event is raised.
* An optional scope can be provided to serve as the this
pointer
* in which the function will execute.
*
* @param {Function} listener The function to be executed when the event is raised.
* @param {Object} [scope] An optional object scope to serve as the this
* pointer in which the listener function will execute.
* @returns {Event~RemoveCallback} A function that will remove this event listener when invoked.
*
* @see Event#raiseEvent
* @see Event#removeEventListener
*/
Event.prototype.addEventListener = function(listener, scope) {
if (typeof listener !== 'function') {
throw new DeveloperError('listener is required and must be a function.');
}
this._listeners.push(listener);
this._scopes.push(scope);
var event = this;
return function() {
event.removeEventListener(listener, scope);
};
};
/**
* Unregisters a previously registered callback.
*
* @param {Function} listener The function to be unregistered.
* @param {Object} [scope] The scope that was originally passed to addEventListener.
* @returns {Boolean} true
if the listener was removed; false
if the listener and scope are not registered with the event.
*
* @see Event#addEventListener
* @see Event#raiseEvent
*/
Event.prototype.removeEventListener = function(listener, scope) {
if (typeof listener !== 'function') {
throw new DeveloperError('listener is required and must be a function.');
}
var listeners = this._listeners;
var scopes = this._scopes;
var index = -1;
for (var i = 0; i < listeners.length; i++) {
if (listeners[i] === listener && scopes[i] === scope) {
index = i;
break;
}
}
if (index !== -1) {
if (this._insideRaiseEvent) {
//In order to allow removing an event subscription from within
//a callback, we don't actually remove the items here. Instead
//remember the index they are at and undefined their value.
this._toRemove.push(index);
listeners[index] = undefined;
scopes[index] = undefined;
} else {
listeners.splice(index, 1);
scopes.splice(index, 1);
}
return true;
}
return false;
};
/**
* Raises the event by calling each registered listener with all supplied arguments.
*
* @param {*} arguments This method takes any number of parameters and passes them through to the listener functions.
*
* @see Event#addEventListener
* @see Event#removeEventListener
*/
Event.prototype.raiseEvent = function() {
this._insideRaiseEvent = true;
var i;
var listeners = this._listeners;
var scopes = this._scopes;
var length = listeners.length;
for (i = 0; i < length; i++) {
var listener = listeners[i];
if (defined(listener)) {
listeners[i].apply(scopes[i], arguments);
}
}
//Actually remove items removed in removeEventListener.
var toRemove = this._toRemove;
length = toRemove.length;
for (i = 0; i < length; i++) {
var index = toRemove[i];
listeners.splice(index, 1);
scopes.splice(index, 1);
}
toRemove.length = 0;
this._insideRaiseEvent = false;
};
/**
* A function that removes a listener.
* @callback Event~RemoveCallback
*/
return Event;
});
/*global define*/
define('Core/Cartesian2',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./freezeObject',
'./Math'
], function(
defaultValue,
defined,
DeveloperError,
freezeObject,
CesiumMath) {
'use strict';
/**
* A 2D Cartesian point.
* @alias Cartesian2
* @constructor
*
* @param {Number} [x=0.0] The X component.
* @param {Number} [y=0.0] The Y component.
*
* @see Cartesian3
* @see Cartesian4
* @see Packable
*/
function Cartesian2(x, y) {
/**
* The X component.
* @type {Number}
* @default 0.0
*/
this.x = defaultValue(x, 0.0);
/**
* The Y component.
* @type {Number}
* @default 0.0
*/
this.y = defaultValue(y, 0.0);
}
/**
* Creates a Cartesian2 instance from x and y coordinates.
*
* @param {Number} x The x coordinate.
* @param {Number} y The y coordinate.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
*/
Cartesian2.fromElements = function(x, y, result) {
if (!defined(result)) {
return new Cartesian2(x, y);
}
result.x = x;
result.y = y;
return result;
};
/**
* Duplicates a Cartesian2 instance.
*
* @param {Cartesian2} cartesian The Cartesian to duplicate.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. (Returns undefined if cartesian is undefined)
*/
Cartesian2.clone = function(cartesian, result) {
if (!defined(cartesian)) {
return undefined;
}
if (!defined(result)) {
return new Cartesian2(cartesian.x, cartesian.y);
}
result.x = cartesian.x;
result.y = cartesian.y;
return result;
};
/**
* Creates a Cartesian2 instance from an existing Cartesian3. This simply takes the
* x and y properties of the Cartesian3 and drops z.
* @function
*
* @param {Cartesian3} cartesian The Cartesian3 instance to create a Cartesian2 instance from.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
*/
Cartesian2.fromCartesian3 = Cartesian2.clone;
/**
* Creates a Cartesian2 instance from an existing Cartesian4. This simply takes the
* x and y properties of the Cartesian4 and drops z and w.
* @function
*
* @param {Cartesian4} cartesian The Cartesian4 instance to create a Cartesian2 instance from.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
*/
Cartesian2.fromCartesian4 = Cartesian2.clone;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Cartesian2.packedLength = 2;
/**
* Stores the provided instance into the provided array.
*
* @param {Cartesian2} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Cartesian2.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.x;
array[startingIndex] = value.y;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Cartesian2} [result] The object into which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
*/
Cartesian2.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Cartesian2();
}
result.x = array[startingIndex++];
result.y = array[startingIndex];
return result;
};
/**
* Flattens an array of Cartesian2s into and array of components.
*
* @param {Cartesian2[]} array The array of cartesians to pack.
* @param {Number[]} result The array onto which to store the result.
* @returns {Number[]} The packed array.
*/
Cartesian2.packArray = function(array, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
var length = array.length;
if (!defined(result)) {
result = new Array(length * 2);
} else {
result.length = length * 2;
}
for (var i = 0; i < length; ++i) {
Cartesian2.pack(array[i], result, i * 2);
}
return result;
};
/**
* Unpacks an array of cartesian components into and array of Cartesian2s.
*
* @param {Number[]} array The array of components to unpack.
* @param {Cartesian2[]} result The array onto which to store the result.
* @returns {Cartesian2[]} The unpacked array.
*/
Cartesian2.unpackArray = function(array, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
var length = array.length;
if (!defined(result)) {
result = new Array(length / 2);
} else {
result.length = length / 2;
}
for (var i = 0; i < length; i += 2) {
var index = i / 2;
result[index] = Cartesian2.unpack(array, i, result[index]);
}
return result;
};
/**
* Creates a Cartesian2 from two consecutive elements in an array.
* @function
*
* @param {Number[]} array The array whose two consecutive elements correspond to the x and y components, respectively.
* @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to the x component.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
*
* @example
* // Create a Cartesian2 with (1.0, 2.0)
* var v = [1.0, 2.0];
* var p = Cesium.Cartesian2.fromArray(v);
*
* // Create a Cartesian2 with (1.0, 2.0) using an offset into an array
* var v2 = [0.0, 0.0, 1.0, 2.0];
* var p2 = Cesium.Cartesian2.fromArray(v2, 2);
*/
Cartesian2.fromArray = Cartesian2.unpack;
/**
* Computes the value of the maximum component for the supplied Cartesian.
*
* @param {Cartesian2} cartesian The cartesian to use.
* @returns {Number} The value of the maximum component.
*/
Cartesian2.maximumComponent = function(cartesian) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
return Math.max(cartesian.x, cartesian.y);
};
/**
* Computes the value of the minimum component for the supplied Cartesian.
*
* @param {Cartesian2} cartesian The cartesian to use.
* @returns {Number} The value of the minimum component.
*/
Cartesian2.minimumComponent = function(cartesian) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
return Math.min(cartesian.x, cartesian.y);
};
/**
* Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
*
* @param {Cartesian2} first A cartesian to compare.
* @param {Cartesian2} second A cartesian to compare.
* @param {Cartesian2} result The object into which to store the result.
* @returns {Cartesian2} A cartesian with the minimum components.
*/
Cartesian2.minimumByComponent = function(first, second, result) {
if (!defined(first)) {
throw new DeveloperError('first is required.');
}
if (!defined(second)) {
throw new DeveloperError('second is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
result.x = Math.min(first.x, second.x);
result.y = Math.min(first.y, second.y);
return result;
};
/**
* Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
*
* @param {Cartesian2} first A cartesian to compare.
* @param {Cartesian2} second A cartesian to compare.
* @param {Cartesian2} result The object into which to store the result.
* @returns {Cartesian2} A cartesian with the maximum components.
*/
Cartesian2.maximumByComponent = function(first, second, result) {
if (!defined(first)) {
throw new DeveloperError('first is required.');
}
if (!defined(second)) {
throw new DeveloperError('second is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
result.x = Math.max(first.x, second.x);
result.y = Math.max(first.y, second.y);
return result;
};
/**
* Computes the provided Cartesian's squared magnitude.
*
* @param {Cartesian2} cartesian The Cartesian instance whose squared magnitude is to be computed.
* @returns {Number} The squared magnitude.
*/
Cartesian2.magnitudeSquared = function(cartesian) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
return cartesian.x * cartesian.x + cartesian.y * cartesian.y;
};
/**
* Computes the Cartesian's magnitude (length).
*
* @param {Cartesian2} cartesian The Cartesian instance whose magnitude is to be computed.
* @returns {Number} The magnitude.
*/
Cartesian2.magnitude = function(cartesian) {
return Math.sqrt(Cartesian2.magnitudeSquared(cartesian));
};
var distanceScratch = new Cartesian2();
/**
* Computes the distance between two points.
*
* @param {Cartesian2} left The first point to compute the distance from.
* @param {Cartesian2} right The second point to compute the distance to.
* @returns {Number} The distance between two points.
*
* @example
* // Returns 1.0
* var d = Cesium.Cartesian2.distance(new Cesium.Cartesian2(1.0, 0.0), new Cesium.Cartesian2(2.0, 0.0));
*/
Cartesian2.distance = function(left, right) {
if (!defined(left) || !defined(right)) {
throw new DeveloperError('left and right are required.');
}
Cartesian2.subtract(left, right, distanceScratch);
return Cartesian2.magnitude(distanceScratch);
};
/**
* Computes the squared distance between two points. Comparing squared distances
* using this function is more efficient than comparing distances using {@link Cartesian2#distance}.
*
* @param {Cartesian2} left The first point to compute the distance from.
* @param {Cartesian2} right The second point to compute the distance to.
* @returns {Number} The distance between two points.
*
* @example
* // Returns 4.0, not 2.0
* var d = Cesium.Cartesian2.distance(new Cesium.Cartesian2(1.0, 0.0), new Cesium.Cartesian2(3.0, 0.0));
*/
Cartesian2.distanceSquared = function(left, right) {
if (!defined(left) || !defined(right)) {
throw new DeveloperError('left and right are required.');
}
Cartesian2.subtract(left, right, distanceScratch);
return Cartesian2.magnitudeSquared(distanceScratch);
};
/**
* Computes the normalized form of the supplied Cartesian.
*
* @param {Cartesian2} cartesian The Cartesian to be normalized.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.normalize = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var magnitude = Cartesian2.magnitude(cartesian);
result.x = cartesian.x / magnitude;
result.y = cartesian.y / magnitude;
if (isNaN(result.x) || isNaN(result.y)) {
throw new DeveloperError('normalized result is not a number');
}
return result;
};
/**
* Computes the dot (scalar) product of two Cartesians.
*
* @param {Cartesian2} left The first Cartesian.
* @param {Cartesian2} right The second Cartesian.
* @returns {Number} The dot product.
*/
Cartesian2.dot = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
return left.x * right.x + left.y * right.y;
};
/**
* Computes the componentwise product of two Cartesians.
*
* @param {Cartesian2} left The first Cartesian.
* @param {Cartesian2} right The second Cartesian.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.multiplyComponents = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x * right.x;
result.y = left.y * right.y;
return result;
};
/**
* Computes the componentwise quotient of two Cartesians.
*
* @param {Cartesian2} left The first Cartesian.
* @param {Cartesian2} right The second Cartesian.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.divideComponents = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x / right.x;
result.y = left.y / right.y;
return result;
};
/**
* Computes the componentwise sum of two Cartesians.
*
* @param {Cartesian2} left The first Cartesian.
* @param {Cartesian2} right The second Cartesian.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x + right.x;
result.y = left.y + right.y;
return result;
};
/**
* Computes the componentwise difference of two Cartesians.
*
* @param {Cartesian2} left The first Cartesian.
* @param {Cartesian2} right The second Cartesian.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x - right.x;
result.y = left.y - right.y;
return result;
};
/**
* Multiplies the provided Cartesian componentwise by the provided scalar.
*
* @param {Cartesian2} cartesian The Cartesian to be scaled.
* @param {Number} scalar The scalar to multiply with.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.multiplyByScalar = function(cartesian, scalar, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = cartesian.x * scalar;
result.y = cartesian.y * scalar;
return result;
};
/**
* Divides the provided Cartesian componentwise by the provided scalar.
*
* @param {Cartesian2} cartesian The Cartesian to be divided.
* @param {Number} scalar The scalar to divide by.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.divideByScalar = function(cartesian, scalar, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = cartesian.x / scalar;
result.y = cartesian.y / scalar;
return result;
};
/**
* Negates the provided Cartesian.
*
* @param {Cartesian2} cartesian The Cartesian to be negated.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.negate = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = -cartesian.x;
result.y = -cartesian.y;
return result;
};
/**
* Computes the absolute value of the provided Cartesian.
*
* @param {Cartesian2} cartesian The Cartesian whose absolute value is to be computed.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.abs = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = Math.abs(cartesian.x);
result.y = Math.abs(cartesian.y);
return result;
};
var lerpScratch = new Cartesian2();
/**
* Computes the linear interpolation or extrapolation at t using the provided cartesians.
*
* @param {Cartesian2} start The value corresponding to t at 0.0.
* @param {Cartesian2} end The value corresponding to t at 1.0.
* @param {Number} t The point along t at which to interpolate.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Cartesian2.lerp = function(start, end, t, result) {
if (!defined(start)) {
throw new DeveloperError('start is required.');
}
if (!defined(end)) {
throw new DeveloperError('end is required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
Cartesian2.multiplyByScalar(end, t, lerpScratch);
result = Cartesian2.multiplyByScalar(start, 1.0 - t, result);
return Cartesian2.add(lerpScratch, result, result);
};
var angleBetweenScratch = new Cartesian2();
var angleBetweenScratch2 = new Cartesian2();
/**
* Returns the angle, in radians, between the provided Cartesians.
*
* @param {Cartesian2} left The first Cartesian.
* @param {Cartesian2} right The second Cartesian.
* @returns {Number} The angle between the Cartesians.
*/
Cartesian2.angleBetween = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
Cartesian2.normalize(left, angleBetweenScratch);
Cartesian2.normalize(right, angleBetweenScratch2);
return CesiumMath.acosClamped(Cartesian2.dot(angleBetweenScratch, angleBetweenScratch2));
};
var mostOrthogonalAxisScratch = new Cartesian2();
/**
* Returns the axis that is most orthogonal to the provided Cartesian.
*
* @param {Cartesian2} cartesian The Cartesian on which to find the most orthogonal axis.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The most orthogonal axis.
*/
Cartesian2.mostOrthogonalAxis = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var f = Cartesian2.normalize(cartesian, mostOrthogonalAxisScratch);
Cartesian2.abs(f, f);
if (f.x <= f.y) {
result = Cartesian2.clone(Cartesian2.UNIT_X, result);
} else {
result = Cartesian2.clone(Cartesian2.UNIT_Y, result);
}
return result;
};
/**
* Compares the provided Cartesians componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartesian2} [left] The first Cartesian.
* @param {Cartesian2} [right] The second Cartesian.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Cartesian2.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.x === right.x) &&
(left.y === right.y));
};
/**
* @private
*/
Cartesian2.equalsArray = function(cartesian, array, offset) {
return cartesian.x === array[offset] &&
cartesian.y === array[offset + 1];
};
/**
* Compares the provided Cartesians componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {Cartesian2} [left] The first Cartesian.
* @param {Cartesian2} [right] The second Cartesian.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Cartesian2.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
return (left === right) ||
(defined(left) &&
defined(right) &&
CesiumMath.equalsEpsilon(left.x, right.x, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.y, right.y, relativeEpsilon, absoluteEpsilon));
};
/**
* An immutable Cartesian2 instance initialized to (0.0, 0.0).
*
* @type {Cartesian2}
* @constant
*/
Cartesian2.ZERO = freezeObject(new Cartesian2(0.0, 0.0));
/**
* An immutable Cartesian2 instance initialized to (1.0, 0.0).
*
* @type {Cartesian2}
* @constant
*/
Cartesian2.UNIT_X = freezeObject(new Cartesian2(1.0, 0.0));
/**
* An immutable Cartesian2 instance initialized to (0.0, 1.0).
*
* @type {Cartesian2}
* @constant
*/
Cartesian2.UNIT_Y = freezeObject(new Cartesian2(0.0, 1.0));
/**
* Duplicates this Cartesian2 instance.
*
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
*/
Cartesian2.prototype.clone = function(result) {
return Cartesian2.clone(this, result);
};
/**
* Compares this Cartesian against the provided Cartesian componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartesian2} [right] The right hand side Cartesian.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Cartesian2.prototype.equals = function(right) {
return Cartesian2.equals(this, right);
};
/**
* Compares this Cartesian against the provided Cartesian componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {Cartesian2} [right] The right hand side Cartesian.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
Cartesian2.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
return Cartesian2.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
};
/**
* Creates a string representing this Cartesian in the format '(x, y)'.
*
* @returns {String} A string representing the provided Cartesian in the format '(x, y)'.
*/
Cartesian2.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ')';
};
return Cartesian2;
});
/*global define*/
define('Core/GeographicProjection',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid) {
'use strict';
/**
* A simple map projection where longitude and latitude are linearly mapped to X and Y by multiplying
* them by the {@link Ellipsoid#maximumRadius}. This projection
* is commonly known as geographic, equirectangular, equidistant cylindrical, or plate carrée. It
* is also known as EPSG:4326.
*
* @alias GeographicProjection
* @constructor
*
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid.
*
* @see WebMercatorProjection
*/
function GeographicProjection(ellipsoid) {
this._ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
this._semimajorAxis = this._ellipsoid.maximumRadius;
this._oneOverSemimajorAxis = 1.0 / this._semimajorAxis;
}
defineProperties(GeographicProjection.prototype, {
/**
* Gets the {@link Ellipsoid}.
*
* @memberof GeographicProjection.prototype
*
* @type {Ellipsoid}
* @readonly
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
}
});
/**
* Projects a set of {@link Cartographic} coordinates, in radians, to map coordinates, in meters.
* X and Y are the longitude and latitude, respectively, multiplied by the maximum radius of the
* ellipsoid. Z is the unmodified height.
*
* @param {Cartographic} cartographic The coordinates to project.
* @param {Cartesian3} [result] An instance into which to copy the result. If this parameter is
* undefined, a new instance is created and returned.
* @returns {Cartesian3} The projected coordinates. If the result parameter is not undefined, the
* coordinates are copied there and that instance is returned. Otherwise, a new instance is
* created and returned.
*/
GeographicProjection.prototype.project = function(cartographic, result) {
// Actually this is the special case of equidistant cylindrical called the plate carree
var semimajorAxis = this._semimajorAxis;
var x = cartographic.longitude * semimajorAxis;
var y = cartographic.latitude * semimajorAxis;
var z = cartographic.height;
if (!defined(result)) {
return new Cartesian3(x, y, z);
}
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Unprojects a set of projected {@link Cartesian3} coordinates, in meters, to {@link Cartographic}
* coordinates, in radians. Longitude and Latitude are the X and Y coordinates, respectively,
* divided by the maximum radius of the ellipsoid. Height is the unmodified Z coordinate.
*
* @param {Cartesian3} cartesian The Cartesian position to unproject with height (z) in meters.
* @param {Cartographic} [result] An instance into which to copy the result. If this parameter is
* undefined, a new instance is created and returned.
* @returns {Cartographic} The unprojected coordinates. If the result parameter is not undefined, the
* coordinates are copied there and that instance is returned. Otherwise, a new instance is
* created and returned.
*/
GeographicProjection.prototype.unproject = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
var oneOverEarthSemimajorAxis = this._oneOverSemimajorAxis;
var longitude = cartesian.x * oneOverEarthSemimajorAxis;
var latitude = cartesian.y * oneOverEarthSemimajorAxis;
var height = cartesian.z;
if (!defined(result)) {
return new Cartographic(longitude, latitude, height);
}
result.longitude = longitude;
result.latitude = latitude;
result.height = height;
return result;
};
return GeographicProjection;
});
/*global define*/
define('Core/Rectangle',[
'./Cartographic',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./freezeObject',
'./Math'
], function(
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
freezeObject,
CesiumMath) {
'use strict';
/**
* A two dimensional region specified as longitude and latitude coordinates.
*
* @alias Rectangle
* @constructor
*
* @param {Number} [west=0.0] The westernmost longitude, in radians, in the range [-Pi, Pi].
* @param {Number} [south=0.0] The southernmost latitude, in radians, in the range [-Pi/2, Pi/2].
* @param {Number} [east=0.0] The easternmost longitude, in radians, in the range [-Pi, Pi].
* @param {Number} [north=0.0] The northernmost latitude, in radians, in the range [-Pi/2, Pi/2].
*
* @see Packable
*/
function Rectangle(west, south, east, north) {
/**
* The westernmost longitude in radians in the range [-Pi, Pi].
*
* @type {Number}
* @default 0.0
*/
this.west = defaultValue(west, 0.0);
/**
* The southernmost latitude in radians in the range [-Pi/2, Pi/2].
*
* @type {Number}
* @default 0.0
*/
this.south = defaultValue(south, 0.0);
/**
* The easternmost longitude in radians in the range [-Pi, Pi].
*
* @type {Number}
* @default 0.0
*/
this.east = defaultValue(east, 0.0);
/**
* The northernmost latitude in radians in the range [-Pi/2, Pi/2].
*
* @type {Number}
* @default 0.0
*/
this.north = defaultValue(north, 0.0);
}
defineProperties(Rectangle.prototype, {
/**
* Gets the width of the rectangle in radians.
* @memberof Rectangle.prototype
* @type {Number}
*/
width : {
get : function() {
return Rectangle.computeWidth(this);
}
},
/**
* Gets the height of the rectangle in radians.
* @memberof Rectangle.prototype
* @type {Number}
*/
height : {
get : function() {
return Rectangle.computeHeight(this);
}
}
});
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Rectangle.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {Rectangle} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Rectangle.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.west;
array[startingIndex++] = value.south;
array[startingIndex++] = value.east;
array[startingIndex] = value.north;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Rectangle} [result] The object into which to store the result.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if one was not provided.
*/
Rectangle.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Rectangle();
}
result.west = array[startingIndex++];
result.south = array[startingIndex++];
result.east = array[startingIndex++];
result.north = array[startingIndex];
return result;
};
/**
* Computes the width of a rectangle in radians.
* @param {Rectangle} rectangle The rectangle to compute the width of.
* @returns {Number} The width.
*/
Rectangle.computeWidth = function(rectangle) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
var east = rectangle.east;
var west = rectangle.west;
if (east < west) {
east += CesiumMath.TWO_PI;
}
return east - west;
};
/**
* Computes the height of a rectangle in radians.
* @param {Rectangle} rectangle The rectangle to compute the height of.
* @returns {Number} The height.
*/
Rectangle.computeHeight = function(rectangle) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
return rectangle.north - rectangle.south;
};
/**
* Creates an rectangle given the boundary longitude and latitude in degrees.
*
* @param {Number} [west=0.0] The westernmost longitude in degrees in the range [-180.0, 180.0].
* @param {Number} [south=0.0] The southernmost latitude in degrees in the range [-90.0, 90.0].
* @param {Number} [east=0.0] The easternmost longitude in degrees in the range [-180.0, 180.0].
* @param {Number} [north=0.0] The northernmost latitude in degrees in the range [-90.0, 90.0].
* @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
*
* @example
* var rectangle = Cesium.Rectangle.fromDegrees(0.0, 20.0, 10.0, 30.0);
*/
Rectangle.fromDegrees = function(west, south, east, north, result) {
west = CesiumMath.toRadians(defaultValue(west, 0.0));
south = CesiumMath.toRadians(defaultValue(south, 0.0));
east = CesiumMath.toRadians(defaultValue(east, 0.0));
north = CesiumMath.toRadians(defaultValue(north, 0.0));
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Creates the smallest possible Rectangle that encloses all positions in the provided array.
*
* @param {Cartographic[]} cartographics The list of Cartographic instances.
* @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
*/
Rectangle.fromCartographicArray = function(cartographics, result) {
if (!defined(cartographics)) {
throw new DeveloperError('cartographics is required.');
}
var west = Number.MAX_VALUE;
var east = -Number.MAX_VALUE;
var westOverIDL = Number.MAX_VALUE;
var eastOverIDL = -Number.MAX_VALUE;
var south = Number.MAX_VALUE;
var north = -Number.MAX_VALUE;
for ( var i = 0, len = cartographics.length; i < len; i++) {
var position = cartographics[i];
west = Math.min(west, position.longitude);
east = Math.max(east, position.longitude);
south = Math.min(south, position.latitude);
north = Math.max(north, position.latitude);
var lonAdjusted = position.longitude >= 0 ? position.longitude : position.longitude + CesiumMath.TWO_PI;
westOverIDL = Math.min(westOverIDL, lonAdjusted);
eastOverIDL = Math.max(eastOverIDL, lonAdjusted);
}
if(east - west > eastOverIDL - westOverIDL) {
west = westOverIDL;
east = eastOverIDL;
if (east > CesiumMath.PI) {
east = east - CesiumMath.TWO_PI;
}
if (west > CesiumMath.PI) {
west = west - CesiumMath.TWO_PI;
}
}
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Creates the smallest possible Rectangle that encloses all positions in the provided array.
*
* @param {Cartesian[]} cartesians The list of Cartesian instances.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid the cartesians are on.
* @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
*/
Rectangle.fromCartesianArray = function(cartesians, ellipsoid, result) {
if (!defined(cartesians)) {
throw new DeveloperError('cartesians is required.');
}
var west = Number.MAX_VALUE;
var east = -Number.MAX_VALUE;
var westOverIDL = Number.MAX_VALUE;
var eastOverIDL = -Number.MAX_VALUE;
var south = Number.MAX_VALUE;
var north = -Number.MAX_VALUE;
for ( var i = 0, len = cartesians.length; i < len; i++) {
var position = ellipsoid.cartesianToCartographic(cartesians[i]);
west = Math.min(west, position.longitude);
east = Math.max(east, position.longitude);
south = Math.min(south, position.latitude);
north = Math.max(north, position.latitude);
var lonAdjusted = position.longitude >= 0 ? position.longitude : position.longitude + CesiumMath.TWO_PI;
westOverIDL = Math.min(westOverIDL, lonAdjusted);
eastOverIDL = Math.max(eastOverIDL, lonAdjusted);
}
if(east - west > eastOverIDL - westOverIDL) {
west = westOverIDL;
east = eastOverIDL;
if (east > CesiumMath.PI) {
east = east - CesiumMath.TWO_PI;
}
if (west > CesiumMath.PI) {
west = west - CesiumMath.TWO_PI;
}
}
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Duplicates an Rectangle.
*
* @param {Rectangle} rectangle The rectangle to clone.
* @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided. (Returns undefined if rectangle is undefined)
*/
Rectangle.clone = function(rectangle, result) {
if (!defined(rectangle)) {
return undefined;
}
if (!defined(result)) {
return new Rectangle(rectangle.west, rectangle.south, rectangle.east, rectangle.north);
}
result.west = rectangle.west;
result.south = rectangle.south;
result.east = rectangle.east;
result.north = rectangle.north;
return result;
};
/**
* Duplicates this Rectangle.
*
* @param {Rectangle} [result] The object onto which to store the result.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
*/
Rectangle.prototype.clone = function(result) {
return Rectangle.clone(this, result);
};
/**
* Compares the provided Rectangle with this Rectangle componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Rectangle} [other] The Rectangle to compare.
* @returns {Boolean} true
if the Rectangles are equal, false
otherwise.
*/
Rectangle.prototype.equals = function(other) {
return Rectangle.equals(this, other);
};
/**
* Compares the provided rectangles and returns true
if they are equal,
* false
otherwise.
*
* @param {Rectangle} [left] The first Rectangle.
* @param {Rectangle} [right] The second Rectangle.
* @returns {Boolean} true
if left and right are equal; otherwise false
.
*/
Rectangle.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.west === right.west) &&
(left.south === right.south) &&
(left.east === right.east) &&
(left.north === right.north));
};
/**
* Compares the provided Rectangle with this Rectangle componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Rectangle} [other] The Rectangle to compare.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if the Rectangles are within the provided epsilon, false
otherwise.
*/
Rectangle.prototype.equalsEpsilon = function(other, epsilon) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon is required and must be a number.');
}
return defined(other) &&
(Math.abs(this.west - other.west) <= epsilon) &&
(Math.abs(this.south - other.south) <= epsilon) &&
(Math.abs(this.east - other.east) <= epsilon) &&
(Math.abs(this.north - other.north) <= epsilon);
};
/**
* Checks an Rectangle's properties and throws if they are not in valid ranges.
*
* @param {Rectangle} rectangle The rectangle to validate
*
* @exception {DeveloperError} north
must be in the interval [-Pi/2
, Pi/2
].
* @exception {DeveloperError} south
must be in the interval [-Pi/2
, Pi/2
].
* @exception {DeveloperError} east
must be in the interval [-Pi
, Pi
].
* @exception {DeveloperError} west
must be in the interval [-Pi
, Pi
].
*/
Rectangle.validate = function(rectangle) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
var north = rectangle.north;
if (typeof north !== 'number') {
throw new DeveloperError('north is required to be a number.');
}
if (north < -CesiumMath.PI_OVER_TWO || north > CesiumMath.PI_OVER_TWO) {
throw new DeveloperError('north must be in the interval [-Pi/2, Pi/2].');
}
var south = rectangle.south;
if (typeof south !== 'number') {
throw new DeveloperError('south is required to be a number.');
}
if (south < -CesiumMath.PI_OVER_TWO || south > CesiumMath.PI_OVER_TWO) {
throw new DeveloperError('south must be in the interval [-Pi/2, Pi/2].');
}
var west = rectangle.west;
if (typeof west !== 'number') {
throw new DeveloperError('west is required to be a number.');
}
if (west < -Math.PI || west > Math.PI) {
throw new DeveloperError('west must be in the interval [-Pi, Pi].');
}
var east = rectangle.east;
if (typeof east !== 'number') {
throw new DeveloperError('east is required to be a number.');
}
if (east < -Math.PI || east > Math.PI) {
throw new DeveloperError('east must be in the interval [-Pi, Pi].');
}
};
/**
* Computes the southwest corner of an rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
*/
Rectangle.southwest = function(rectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(result)) {
return new Cartographic(rectangle.west, rectangle.south);
}
result.longitude = rectangle.west;
result.latitude = rectangle.south;
result.height = 0.0;
return result;
};
/**
* Computes the northwest corner of an rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
*/
Rectangle.northwest = function(rectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(result)) {
return new Cartographic(rectangle.west, rectangle.north);
}
result.longitude = rectangle.west;
result.latitude = rectangle.north;
result.height = 0.0;
return result;
};
/**
* Computes the northeast corner of an rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
*/
Rectangle.northeast = function(rectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(result)) {
return new Cartographic(rectangle.east, rectangle.north);
}
result.longitude = rectangle.east;
result.latitude = rectangle.north;
result.height = 0.0;
return result;
};
/**
* Computes the southeast corner of an rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the corner
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
*/
Rectangle.southeast = function(rectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(result)) {
return new Cartographic(rectangle.east, rectangle.south);
}
result.longitude = rectangle.east;
result.latitude = rectangle.south;
result.height = 0.0;
return result;
};
/**
* Computes the center of an rectangle.
*
* @param {Rectangle} rectangle The rectangle for which to find the center
* @param {Cartographic} [result] The object onto which to store the result.
* @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
*/
Rectangle.center = function(rectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
var east = rectangle.east;
var west = rectangle.west;
if (east < west) {
east += CesiumMath.TWO_PI;
}
var longitude = CesiumMath.negativePiToPi((west + east) * 0.5);
var latitude = (rectangle.south + rectangle.north) * 0.5;
if (!defined(result)) {
return new Cartographic(longitude, latitude);
}
result.longitude = longitude;
result.latitude = latitude;
result.height = 0.0;
return result;
};
/**
* Computes the intersection of two rectangles. This function assumes that the rectangle's coordinates are
* latitude and longitude in radians and produces a correct intersection, taking into account the fact that
* the same angle can be represented with multiple values as well as the wrapping of longitude at the
* anti-meridian. For a simple intersection that ignores these factors and can be used with projected
* coordinates, see {@link Rectangle.simpleIntersection}.
*
* @param {Rectangle} rectangle On rectangle to find an intersection
* @param {Rectangle} otherRectangle Another rectangle to find an intersection
* @param {Rectangle} [result] The object onto which to store the result.
* @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection.
*/
Rectangle.intersection = function(rectangle, otherRectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(otherRectangle)) {
throw new DeveloperError('otherRectangle is required.');
}
var rectangleEast = rectangle.east;
var rectangleWest = rectangle.west;
var otherRectangleEast = otherRectangle.east;
var otherRectangleWest = otherRectangle.west;
if (rectangleEast < rectangleWest && otherRectangleEast > 0.0) {
rectangleEast += CesiumMath.TWO_PI;
} else if (otherRectangleEast < otherRectangleWest && rectangleEast > 0.0) {
otherRectangleEast += CesiumMath.TWO_PI;
}
if (rectangleEast < rectangleWest && otherRectangleWest < 0.0) {
otherRectangleWest += CesiumMath.TWO_PI;
} else if (otherRectangleEast < otherRectangleWest && rectangleWest < 0.0) {
rectangleWest += CesiumMath.TWO_PI;
}
var west = CesiumMath.negativePiToPi(Math.max(rectangleWest, otherRectangleWest));
var east = CesiumMath.negativePiToPi(Math.min(rectangleEast, otherRectangleEast));
if ((rectangle.west < rectangle.east || otherRectangle.west < otherRectangle.east) && east <= west) {
return undefined;
}
var south = Math.max(rectangle.south, otherRectangle.south);
var north = Math.min(rectangle.north, otherRectangle.north);
if (south >= north) {
return undefined;
}
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Computes a simple intersection of two rectangles. Unlike {@link Rectangle.intersection}, this function
* does not attempt to put the angular coordinates into a consistent range or to account for crossing the
* anti-meridian. As such, it can be used for rectangles where the coordinates are not simply latitude
* and longitude (i.e. projected coordinates).
*
* @param {Rectangle} rectangle On rectangle to find an intersection
* @param {Rectangle} otherRectangle Another rectangle to find an intersection
* @param {Rectangle} [result] The object onto which to store the result.
* @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection.
*/
Rectangle.simpleIntersection = function(rectangle, otherRectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(otherRectangle)) {
throw new DeveloperError('otherRectangle is required.');
}
var west = Math.max(rectangle.west, otherRectangle.west);
var south = Math.max(rectangle.south, otherRectangle.south);
var east = Math.min(rectangle.east, otherRectangle.east);
var north = Math.min(rectangle.north, otherRectangle.north);
if (south >= north || west >= east) {
return undefined;
}
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Computes a rectangle that is the union of two rectangles.
*
* @param {Rectangle} rectangle A rectangle to enclose in rectangle.
* @param {Rectangle} otherRectangle A rectangle to enclose in a rectangle.
* @param {Rectangle} [result] The object onto which to store the result.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
*/
Rectangle.union = function(rectangle, otherRectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(otherRectangle)) {
throw new DeveloperError('otherRectangle is required.');
}
if (!defined(result)) {
result = new Rectangle();
}
var rectangleEast = rectangle.east;
var rectangleWest = rectangle.west;
var otherRectangleEast = otherRectangle.east;
var otherRectangleWest = otherRectangle.west;
if (rectangleEast < rectangleWest && otherRectangleEast > 0.0) {
rectangleEast += CesiumMath.TWO_PI;
} else if (otherRectangleEast < otherRectangleWest && rectangleEast > 0.0) {
otherRectangleEast += CesiumMath.TWO_PI;
}
if (rectangleEast < rectangleWest && otherRectangleWest < 0.0) {
otherRectangleWest += CesiumMath.TWO_PI;
} else if (otherRectangleEast < otherRectangleWest && rectangleWest < 0.0) {
rectangleWest += CesiumMath.TWO_PI;
}
var west = CesiumMath.convertLongitudeRange(Math.min(rectangleWest, otherRectangleWest));
var east = CesiumMath.convertLongitudeRange(Math.max(rectangleEast, otherRectangleEast));
result.west = west;
result.south = Math.min(rectangle.south, otherRectangle.south);
result.east = east;
result.north = Math.max(rectangle.north, otherRectangle.north);
return result;
};
/**
* Computes a rectangle by enlarging the provided rectangle until it contains the provided cartographic.
*
* @param {Rectangle} rectangle A rectangle to expand.
* @param {Cartographic} cartographic A cartographic to enclose in a rectangle.
* @param {Rectangle} [result] The object onto which to store the result.
* @returns {Rectangle} The modified result parameter or a new Rectangle instance if one was not provided.
*/
Rectangle.expand = function(rectangle, cartographic, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
if (!defined(cartographic)) {
throw new DeveloperError('cartographic is required.');
}
if (!defined(result)) {
result = new Rectangle();
}
result.west = Math.min(rectangle.west, cartographic.longitude);
result.south = Math.min(rectangle.south, cartographic.latitude);
result.east = Math.max(rectangle.east, cartographic.longitude);
result.north = Math.max(rectangle.north, cartographic.latitude);
return result;
};
/**
* Returns true if the cartographic is on or inside the rectangle, false otherwise.
*
* @param {Rectangle} rectangle The rectangle
* @param {Cartographic} cartographic The cartographic to test.
* @returns {Boolean} true if the provided cartographic is inside the rectangle, false otherwise.
*/
Rectangle.contains = function(rectangle, cartographic) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (!defined(cartographic)) {
throw new DeveloperError('cartographic is required.');
}
var longitude = cartographic.longitude;
var latitude = cartographic.latitude;
var west = rectangle.west;
var east = rectangle.east;
if (east < west) {
east += CesiumMath.TWO_PI;
if (longitude < 0.0) {
longitude += CesiumMath.TWO_PI;
}
}
return (longitude > west || CesiumMath.equalsEpsilon(longitude, west, CesiumMath.EPSILON14)) &&
(longitude < east || CesiumMath.equalsEpsilon(longitude, east, CesiumMath.EPSILON14)) &&
latitude >= rectangle.south &&
latitude <= rectangle.north;
};
var subsampleLlaScratch = new Cartographic();
/**
* Samples an rectangle so that it includes a list of Cartesian points suitable for passing to
* {@link BoundingSphere#fromPoints}. Sampling is necessary to account
* for rectangles that cover the poles or cross the equator.
*
* @param {Rectangle} rectangle The rectangle to subsample.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use.
* @param {Number} [surfaceHeight=0.0] The height of the rectangle above the ellipsoid.
* @param {Cartesian3[]} [result] The array of Cartesians onto which to store the result.
* @returns {Cartesian3[]} The modified result parameter or a new Array of Cartesians instances if none was provided.
*/
Rectangle.subsample = function(rectangle, ellipsoid, surfaceHeight, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
surfaceHeight = defaultValue(surfaceHeight, 0.0);
if (!defined(result)) {
result = [];
}
var length = 0;
var north = rectangle.north;
var south = rectangle.south;
var east = rectangle.east;
var west = rectangle.west;
var lla = subsampleLlaScratch;
lla.height = surfaceHeight;
lla.longitude = west;
lla.latitude = north;
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
lla.longitude = east;
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
lla.latitude = south;
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
lla.longitude = west;
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
if (north < 0.0) {
lla.latitude = north;
} else if (south > 0.0) {
lla.latitude = south;
} else {
lla.latitude = 0.0;
}
for ( var i = 1; i < 8; ++i) {
lla.longitude = -Math.PI + i * CesiumMath.PI_OVER_TWO;
if (Rectangle.contains(rectangle, lla)) {
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
}
}
if (lla.latitude === 0.0) {
lla.longitude = west;
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
lla.longitude = east;
result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
length++;
}
result.length = length;
return result;
};
/**
* The largest possible rectangle.
*
* @type {Rectangle}
* @constant
*/
Rectangle.MAX_VALUE = freezeObject(new Rectangle(-Math.PI, -CesiumMath.PI_OVER_TWO, Math.PI, CesiumMath.PI_OVER_TWO));
return Rectangle;
});
/*global define*/
define('Core/GeographicTilingScheme',[
'./Cartesian2',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./GeographicProjection',
'./Math',
'./Rectangle'
], function(
Cartesian2,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
GeographicProjection,
CesiumMath,
Rectangle) {
'use strict';
/**
* A tiling scheme for geometry referenced to a simple {@link GeographicProjection} where
* longitude and latitude are directly mapped to X and Y. This projection is commonly
* known as geographic, equirectangular, equidistant cylindrical, or plate carrée.
*
* @alias GeographicTilingScheme
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
* the WGS84 ellipsoid.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the tiling scheme.
* @param {Number} [options.numberOfLevelZeroTilesX=2] The number of tiles in the X direction at level zero of
* the tile tree.
* @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
* the tile tree.
*/
function GeographicTilingScheme(options) {
options = defaultValue(options, {});
this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
this._rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
this._projection = new GeographicProjection(this._ellipsoid);
this._numberOfLevelZeroTilesX = defaultValue(options.numberOfLevelZeroTilesX, 2);
this._numberOfLevelZeroTilesY = defaultValue(options.numberOfLevelZeroTilesY, 1);
}
defineProperties(GeographicTilingScheme.prototype, {
/**
* Gets the ellipsoid that is tiled by this tiling scheme.
* @memberof GeographicTilingScheme.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Gets the rectangle, in radians, covered by this tiling scheme.
* @memberof GeographicTilingScheme.prototype
* @type {Rectangle}
*/
rectangle : {
get : function() {
return this._rectangle;
}
},
/**
* Gets the map projection used by this tiling scheme.
* @memberof GeographicTilingScheme.prototype
* @type {MapProjection}
*/
projection : {
get : function() {
return this._projection;
}
}
});
/**
* Gets the total number of tiles in the X direction at a specified level-of-detail.
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the X direction at the given level.
*/
GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel = function(level) {
return this._numberOfLevelZeroTilesX << level;
};
/**
* Gets the total number of tiles in the Y direction at a specified level-of-detail.
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the Y direction at the given level.
*/
GeographicTilingScheme.prototype.getNumberOfYTilesAtLevel = function(level) {
return this._numberOfLevelZeroTilesY << level;
};
/**
* Transforms an rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
*
* @param {Rectangle} rectangle The rectangle to transform.
* @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
* is undefined.
*/
GeographicTilingScheme.prototype.rectangleToNativeRectangle = function(rectangle, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
var west = CesiumMath.toDegrees(rectangle.west);
var south = CesiumMath.toDegrees(rectangle.south);
var east = CesiumMath.toDegrees(rectangle.east);
var north = CesiumMath.toDegrees(rectangle.north);
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
* of the tiling scheme.
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
GeographicTilingScheme.prototype.tileXYToNativeRectangle = function(x, y, level, result) {
var rectangleRadians = this.tileXYToRectangle(x, y, level, result);
rectangleRadians.west = CesiumMath.toDegrees(rectangleRadians.west);
rectangleRadians.south = CesiumMath.toDegrees(rectangleRadians.south);
rectangleRadians.east = CesiumMath.toDegrees(rectangleRadians.east);
rectangleRadians.north = CesiumMath.toDegrees(rectangleRadians.north);
return rectangleRadians;
};
/**
* Converts tile x, y coordinates and level to a cartographic rectangle in radians.
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
GeographicTilingScheme.prototype.tileXYToRectangle = function(x, y, level, result) {
var rectangle = this._rectangle;
var xTiles = this.getNumberOfXTilesAtLevel(level);
var yTiles = this.getNumberOfYTilesAtLevel(level);
var xTileWidth = rectangle.width / xTiles;
var west = x * xTileWidth + rectangle.west;
var east = (x + 1) * xTileWidth + rectangle.west;
var yTileHeight = rectangle.height / yTiles;
var north = rectangle.north - y * yTileHeight;
var south = rectangle.north - (y + 1) * yTileHeight;
if (!defined(result)) {
result = new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Calculates the tile x, y coordinates of the tile containing
* a given cartographic position.
*
* @param {Cartographic} position The position.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
* if 'result' is undefined.
*/
GeographicTilingScheme.prototype.positionToTileXY = function(position, level, result) {
var rectangle = this._rectangle;
if (!Rectangle.contains(rectangle, position)) {
// outside the bounds of the tiling scheme
return undefined;
}
var xTiles = this.getNumberOfXTilesAtLevel(level);
var yTiles = this.getNumberOfYTilesAtLevel(level);
var xTileWidth = rectangle.width / xTiles;
var yTileHeight = rectangle.height / yTiles;
var longitude = position.longitude;
if (rectangle.east < rectangle.west) {
longitude += CesiumMath.TWO_PI;
}
var xTileCoordinate = (longitude - rectangle.west) / xTileWidth | 0;
if (xTileCoordinate >= xTiles) {
xTileCoordinate = xTiles - 1;
}
var yTileCoordinate = (rectangle.north - position.latitude) / yTileHeight | 0;
if (yTileCoordinate >= yTiles) {
yTileCoordinate = yTiles - 1;
}
if (!defined(result)) {
return new Cartesian2(xTileCoordinate, yTileCoordinate);
}
result.x = xTileCoordinate;
result.y = yTileCoordinate;
return result;
};
return GeographicTilingScheme;
});
/*global define*/
define('Core/getImagePixels',[
'./defined'
], function(
defined) {
'use strict';
var context2DsByWidthAndHeight = {};
/**
* Extract a pixel array from a loaded image. Draws the image
* into a canvas so it can read the pixels back.
*
* @exports getImagePixels
*
* @param {Image} image The image to extract pixels from.
* @returns {CanvasPixelArray} The pixels of the image.
*/
function getImagePixels(image, width, height) {
if (!defined(width)) {
width = image.width;
}
if (!defined(height)) {
height = image.height;
}
var context2DsByHeight = context2DsByWidthAndHeight[width];
if (!defined(context2DsByHeight)) {
context2DsByHeight = {};
context2DsByWidthAndHeight[width] = context2DsByHeight;
}
var context2d = context2DsByHeight[height];
if (!defined(context2d)) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
context2d = canvas.getContext('2d');
context2d.globalCompositeOperation = 'copy';
context2DsByHeight[height] = context2d;
}
context2d.drawImage(image, 0, 0, width, height);
return context2d.getImageData(0, 0, width, height).data;
}
return getImagePixels;
});
/*global define*/
define('Core/Intersect',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* This enumerated type is used in determining where, relative to the frustum, an
* object is located. The object can either be fully contained within the frustum (INSIDE),
* partially inside the frustum and partially outside (INTERSECTING), or somwhere entirely
* outside of the frustum's 6 planes (OUTSIDE).
*
* @exports Intersect
*/
var Intersect = {
/**
* Represents that an object is not contained within the frustum.
*
* @type {Number}
* @constant
*/
OUTSIDE : -1,
/**
* Represents that an object intersects one of the frustum's planes.
*
* @type {Number}
* @constant
*/
INTERSECTING : 0,
/**
* Represents that an object is fully within the frustum.
*
* @type {Number}
* @constant
*/
INSIDE : 1
};
return freezeObject(Intersect);
});
/*global define*/
define('Core/AxisAlignedBoundingBox',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Intersect'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
Intersect) {
'use strict';
/**
* Creates an instance of an AxisAlignedBoundingBox from the minimum and maximum points along the x, y, and z axes.
* @alias AxisAlignedBoundingBox
* @constructor
*
* @param {Cartesian3} [minimum=Cartesian3.ZERO] The minimum point along the x, y, and z axes.
* @param {Cartesian3} [maximum=Cartesian3.ZERO] The maximum point along the x, y, and z axes.
* @param {Cartesian3} [center] The center of the box; automatically computed if not supplied.
*
* @see BoundingSphere
* @see BoundingRectangle
*/
function AxisAlignedBoundingBox(minimum, maximum, center) {
/**
* The minimum point defining the bounding box.
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.minimum = Cartesian3.clone(defaultValue(minimum, Cartesian3.ZERO));
/**
* The maximum point defining the bounding box.
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.maximum = Cartesian3.clone(defaultValue(maximum, Cartesian3.ZERO));
//If center was not defined, compute it.
if (!defined(center)) {
center = Cartesian3.add(this.minimum, this.maximum, new Cartesian3());
Cartesian3.multiplyByScalar(center, 0.5, center);
} else {
center = Cartesian3.clone(center);
}
/**
* The center point of the bounding box.
* @type {Cartesian3}
*/
this.center = center;
}
/**
* Computes an instance of an AxisAlignedBoundingBox. The box is determined by
* finding the points spaced the farthest apart on the x, y, and z axes.
*
* @param {Cartesian3[]} positions List of points that the bounding box will enclose. Each point must have a x
, y
, and z
properties.
* @param {AxisAlignedBoundingBox} [result] The object onto which to store the result.
* @returns {AxisAlignedBoundingBox} The modified result parameter or a new AxisAlignedBoundingBox instance if one was not provided.
*
* @example
* // Compute an axis aligned bounding box enclosing two points.
* var box = Cesium.AxisAlignedBoundingBox.fromPoints([new Cesium.Cartesian3(2, 0, 0), new Cesium.Cartesian3(-2, 0, 0)]);
*/
AxisAlignedBoundingBox.fromPoints = function(positions, result) {
if (!defined(result)) {
result = new AxisAlignedBoundingBox();
}
if (!defined(positions) || positions.length === 0) {
result.minimum = Cartesian3.clone(Cartesian3.ZERO, result.minimum);
result.maximum = Cartesian3.clone(Cartesian3.ZERO, result.maximum);
result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
return result;
}
var minimumX = positions[0].x;
var minimumY = positions[0].y;
var minimumZ = positions[0].z;
var maximumX = positions[0].x;
var maximumY = positions[0].y;
var maximumZ = positions[0].z;
var length = positions.length;
for ( var i = 1; i < length; i++) {
var p = positions[i];
var x = p.x;
var y = p.y;
var z = p.z;
minimumX = Math.min(x, minimumX);
maximumX = Math.max(x, maximumX);
minimumY = Math.min(y, minimumY);
maximumY = Math.max(y, maximumY);
minimumZ = Math.min(z, minimumZ);
maximumZ = Math.max(z, maximumZ);
}
var minimum = result.minimum;
minimum.x = minimumX;
minimum.y = minimumY;
minimum.z = minimumZ;
var maximum = result.maximum;
maximum.x = maximumX;
maximum.y = maximumY;
maximum.z = maximumZ;
var center = Cartesian3.add(minimum, maximum, result.center);
Cartesian3.multiplyByScalar(center, 0.5, center);
return result;
};
/**
* Duplicates a AxisAlignedBoundingBox instance.
*
* @param {AxisAlignedBoundingBox} box The bounding box to duplicate.
* @param {AxisAlignedBoundingBox} [result] The object onto which to store the result.
* @returns {AxisAlignedBoundingBox} The modified result parameter or a new AxisAlignedBoundingBox instance if none was provided. (Returns undefined if box is undefined)
*/
AxisAlignedBoundingBox.clone = function(box, result) {
if (!defined(box)) {
return undefined;
}
if (!defined(result)) {
return new AxisAlignedBoundingBox(box.minimum, box.maximum);
}
result.minimum = Cartesian3.clone(box.minimum, result.minimum);
result.maximum = Cartesian3.clone(box.maximum, result.maximum);
result.center = Cartesian3.clone(box.center, result.center);
return result;
};
/**
* Compares the provided AxisAlignedBoundingBox componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {AxisAlignedBoundingBox} [left] The first AxisAlignedBoundingBox.
* @param {AxisAlignedBoundingBox} [right] The second AxisAlignedBoundingBox.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
AxisAlignedBoundingBox.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
Cartesian3.equals(left.center, right.center) &&
Cartesian3.equals(left.minimum, right.minimum) &&
Cartesian3.equals(left.maximum, right.maximum));
};
var intersectScratch = new Cartesian3();
/**
* Determines which side of a plane a box is located.
*
* @param {AxisAlignedBoundingBox} box The bounding box to test.
* @param {Plane} plane The plane to test against.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
* intersects the plane.
*/
AxisAlignedBoundingBox.intersectPlane = function(box, plane) {
if (!defined(box)) {
throw new DeveloperError('box is required.');
}
if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
intersectScratch = Cartesian3.subtract(box.maximum, box.minimum, intersectScratch);
var h = Cartesian3.multiplyByScalar(intersectScratch, 0.5, intersectScratch); //The positive half diagonal
var normal = plane.normal;
var e = h.x * Math.abs(normal.x) + h.y * Math.abs(normal.y) + h.z * Math.abs(normal.z);
var s = Cartesian3.dot(box.center, normal) + plane.distance; //signed distance from center
if (s - e > 0) {
return Intersect.INSIDE;
}
if (s + e < 0) {
//Not in front because normals point inward
return Intersect.OUTSIDE;
}
return Intersect.INTERSECTING;
};
/**
* Duplicates this AxisAlignedBoundingBox instance.
*
* @param {AxisAlignedBoundingBox} [result] The object onto which to store the result.
* @returns {AxisAlignedBoundingBox} The modified result parameter or a new AxisAlignedBoundingBox instance if one was not provided.
*/
AxisAlignedBoundingBox.prototype.clone = function(result) {
return AxisAlignedBoundingBox.clone(this, result);
};
/**
* Determines which side of a plane this box is located.
*
* @param {Plane} plane The plane to test against.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
* intersects the plane.
*/
AxisAlignedBoundingBox.prototype.intersectPlane = function(plane) {
return AxisAlignedBoundingBox.intersectPlane(this, plane);
};
/**
* Compares this AxisAlignedBoundingBox against the provided AxisAlignedBoundingBox componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {AxisAlignedBoundingBox} [right] The right hand side AxisAlignedBoundingBox.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
AxisAlignedBoundingBox.prototype.equals = function(right) {
return AxisAlignedBoundingBox.equals(this, right);
};
return AxisAlignedBoundingBox;
});
/*global define*/
define('Core/Interval',[
'./defaultValue'
], function(
defaultValue) {
'use strict';
/**
* Represents the closed interval [start, stop].
* @alias Interval
* @constructor
*
* @param {Number} [start=0.0] The beginning of the interval.
* @param {Number} [stop=0.0] The end of the interval.
*/
function Interval(start, stop) {
/**
* The beginning of the interval.
* @type {Number}
* @default 0.0
*/
this.start = defaultValue(start, 0.0);
/**
* The end of the interval.
* @type {Number}
* @default 0.0
*/
this.stop = defaultValue(stop, 0.0);
}
return Interval;
});
/*global define*/
define('Core/Matrix3',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./freezeObject',
'./Math'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
freezeObject,
CesiumMath) {
'use strict';
/**
* A 3x3 matrix, indexable as a column-major order array.
* Constructor parameters are in row-major order for code readability.
* @alias Matrix3
* @constructor
*
* @param {Number} [column0Row0=0.0] The value for column 0, row 0.
* @param {Number} [column1Row0=0.0] The value for column 1, row 0.
* @param {Number} [column2Row0=0.0] The value for column 2, row 0.
* @param {Number} [column0Row1=0.0] The value for column 0, row 1.
* @param {Number} [column1Row1=0.0] The value for column 1, row 1.
* @param {Number} [column2Row1=0.0] The value for column 2, row 1.
* @param {Number} [column0Row2=0.0] The value for column 0, row 2.
* @param {Number} [column1Row2=0.0] The value for column 1, row 2.
* @param {Number} [column2Row2=0.0] The value for column 2, row 2.
*
* @see Matrix3.fromColumnMajorArray
* @see Matrix3.fromRowMajorArray
* @see Matrix3.fromQuaternion
* @see Matrix3.fromScale
* @see Matrix3.fromUniformScale
* @see Matrix2
* @see Matrix4
*/
function Matrix3(column0Row0, column1Row0, column2Row0,
column0Row1, column1Row1, column2Row1,
column0Row2, column1Row2, column2Row2) {
this[0] = defaultValue(column0Row0, 0.0);
this[1] = defaultValue(column0Row1, 0.0);
this[2] = defaultValue(column0Row2, 0.0);
this[3] = defaultValue(column1Row0, 0.0);
this[4] = defaultValue(column1Row1, 0.0);
this[5] = defaultValue(column1Row2, 0.0);
this[6] = defaultValue(column2Row0, 0.0);
this[7] = defaultValue(column2Row1, 0.0);
this[8] = defaultValue(column2Row2, 0.0);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Matrix3.packedLength = 9;
/**
* Stores the provided instance into the provided array.
*
* @param {Matrix3} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Matrix3.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value[0];
array[startingIndex++] = value[1];
array[startingIndex++] = value[2];
array[startingIndex++] = value[3];
array[startingIndex++] = value[4];
array[startingIndex++] = value[5];
array[startingIndex++] = value[6];
array[startingIndex++] = value[7];
array[startingIndex++] = value[8];
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Matrix3} [result] The object into which to store the result.
* @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided.
*/
Matrix3.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Matrix3();
}
result[0] = array[startingIndex++];
result[1] = array[startingIndex++];
result[2] = array[startingIndex++];
result[3] = array[startingIndex++];
result[4] = array[startingIndex++];
result[5] = array[startingIndex++];
result[6] = array[startingIndex++];
result[7] = array[startingIndex++];
result[8] = array[startingIndex++];
return result;
};
/**
* Duplicates a Matrix3 instance.
*
* @param {Matrix3} matrix The matrix to duplicate.
* @param {Matrix3} [result] The object onto which to store the result.
* @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided. (Returns undefined if matrix is undefined)
*/
Matrix3.clone = function(values, result) {
if (!defined(values)) {
return undefined;
}
if (!defined(result)) {
return new Matrix3(values[0], values[3], values[6],
values[1], values[4], values[7],
values[2], values[5], values[8]);
}
result[0] = values[0];
result[1] = values[1];
result[2] = values[2];
result[3] = values[3];
result[4] = values[4];
result[5] = values[5];
result[6] = values[6];
result[7] = values[7];
result[8] = values[8];
return result;
};
/**
* Creates a Matrix3 from 9 consecutive elements in an array.
*
* @param {Number[]} array The array whose 9 consecutive elements correspond to the positions of the matrix. Assumes column-major order.
* @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to first column first row position in the matrix.
* @param {Matrix3} [result] The object onto which to store the result.
* @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided.
*
* @example
* // Create the Matrix3:
* // [1.0, 2.0, 3.0]
* // [1.0, 2.0, 3.0]
* // [1.0, 2.0, 3.0]
*
* var v = [1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0];
* var m = Cesium.Matrix3.fromArray(v);
*
* // Create same Matrix3 with using an offset into an array
* var v2 = [0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0];
* var m2 = Cesium.Matrix3.fromArray(v2, 2);
*/
Matrix3.fromArray = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Matrix3();
}
result[0] = array[startingIndex];
result[1] = array[startingIndex + 1];
result[2] = array[startingIndex + 2];
result[3] = array[startingIndex + 3];
result[4] = array[startingIndex + 4];
result[5] = array[startingIndex + 5];
result[6] = array[startingIndex + 6];
result[7] = array[startingIndex + 7];
result[8] = array[startingIndex + 8];
return result;
};
/**
* Creates a Matrix3 instance from a column-major order array.
*
* @param {Number[]} values The column-major order array.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*/
Matrix3.fromColumnMajorArray = function(values, result) {
if (!defined(values)) {
throw new DeveloperError('values parameter is required');
}
return Matrix3.clone(values, result);
};
/**
* Creates a Matrix3 instance from a row-major order array.
* The resulting matrix will be in column-major order.
*
* @param {Number[]} values The row-major order array.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*/
Matrix3.fromRowMajorArray = function(values, result) {
if (!defined(values)) {
throw new DeveloperError('values is required.');
}
if (!defined(result)) {
return new Matrix3(values[0], values[1], values[2],
values[3], values[4], values[5],
values[6], values[7], values[8]);
}
result[0] = values[0];
result[1] = values[3];
result[2] = values[6];
result[3] = values[1];
result[4] = values[4];
result[5] = values[7];
result[6] = values[2];
result[7] = values[5];
result[8] = values[8];
return result;
};
/**
* Computes a 3x3 rotation matrix from the provided quaternion.
*
* @param {Quaternion} quaternion the quaternion to use.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The 3x3 rotation matrix from this quaternion.
*/
Matrix3.fromQuaternion = function(quaternion, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
var x2 = quaternion.x * quaternion.x;
var xy = quaternion.x * quaternion.y;
var xz = quaternion.x * quaternion.z;
var xw = quaternion.x * quaternion.w;
var y2 = quaternion.y * quaternion.y;
var yz = quaternion.y * quaternion.z;
var yw = quaternion.y * quaternion.w;
var z2 = quaternion.z * quaternion.z;
var zw = quaternion.z * quaternion.w;
var w2 = quaternion.w * quaternion.w;
var m00 = x2 - y2 - z2 + w2;
var m01 = 2.0 * (xy - zw);
var m02 = 2.0 * (xz + yw);
var m10 = 2.0 * (xy + zw);
var m11 = -x2 + y2 - z2 + w2;
var m12 = 2.0 * (yz - xw);
var m20 = 2.0 * (xz - yw);
var m21 = 2.0 * (yz + xw);
var m22 = -x2 - y2 + z2 + w2;
if (!defined(result)) {
return new Matrix3(m00, m01, m02,
m10, m11, m12,
m20, m21, m22);
}
result[0] = m00;
result[1] = m10;
result[2] = m20;
result[3] = m01;
result[4] = m11;
result[5] = m21;
result[6] = m02;
result[7] = m12;
result[8] = m22;
return result;
};
/**
* Computes a 3x3 rotation matrix from the provided headingPitchRoll. (see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles )
*
* @param {HeadingPitchRoll} headingPitchRoll the headingPitchRoll to use.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The 3x3 rotation matrix from this headingPitchRoll.
*/
Matrix3.fromHeadingPitchRoll = function(headingPitchRoll, result) {
if (!defined(headingPitchRoll)) {
throw new DeveloperError('headingPitchRoll is required');
}
var cosTheta = Math.cos(-headingPitchRoll.pitch);
var cosPsi = Math.cos(-headingPitchRoll.heading);
var cosPhi = Math.cos(headingPitchRoll.roll);
var sinTheta = Math.sin(-headingPitchRoll.pitch);
var sinPsi = Math.sin(-headingPitchRoll.heading);
var sinPhi = Math.sin(headingPitchRoll.roll);
var m00 = cosTheta * cosPsi;
var m01 = -cosPhi * sinPsi + sinPhi * sinTheta * cosPsi;
var m02 = sinPhi * sinPsi + cosPhi * sinTheta * cosPsi;
var m10 = cosTheta * sinPsi;
var m11 = cosPhi * cosPsi + sinPhi * sinTheta * sinPsi;
var m12 = -sinTheta * cosPhi + cosPhi * sinTheta * sinPsi;
var m20 = -sinTheta;
var m21 = sinPhi * cosTheta;
var m22 = cosPhi * cosTheta;
if (!defined(result)) {
return new Matrix3(m00, m01, m02,
m10, m11, m12,
m20, m21, m22);
}
result[0] = m00;
result[1] = m10;
result[2] = m20;
result[3] = m01;
result[4] = m11;
result[5] = m21;
result[6] = m02;
result[7] = m12;
result[8] = m22;
return result;
};
/**
* Computes a Matrix3 instance representing a non-uniform scale.
*
* @param {Cartesian3} scale The x, y, and z scale factors.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*
* @example
* // Creates
* // [7.0, 0.0, 0.0]
* // [0.0, 8.0, 0.0]
* // [0.0, 0.0, 9.0]
* var m = Cesium.Matrix3.fromScale(new Cesium.Cartesian3(7.0, 8.0, 9.0));
*/
Matrix3.fromScale = function(scale, result) {
if (!defined(scale)) {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
return new Matrix3(
scale.x, 0.0, 0.0,
0.0, scale.y, 0.0,
0.0, 0.0, scale.z);
}
result[0] = scale.x;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = scale.y;
result[5] = 0.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = scale.z;
return result;
};
/**
* Computes a Matrix3 instance representing a uniform scale.
*
* @param {Number} scale The uniform scale factor.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*
* @example
* // Creates
* // [2.0, 0.0, 0.0]
* // [0.0, 2.0, 0.0]
* // [0.0, 0.0, 2.0]
* var m = Cesium.Matrix3.fromUniformScale(2.0);
*/
Matrix3.fromUniformScale = function(scale, result) {
if (typeof scale !== 'number') {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
return new Matrix3(
scale, 0.0, 0.0,
0.0, scale, 0.0,
0.0, 0.0, scale);
}
result[0] = scale;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = scale;
result[5] = 0.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = scale;
return result;
};
/**
* Computes a Matrix3 instance representing the cross product equivalent matrix of a Cartesian3 vector.
*
* @param {Cartesian3} the vector on the left hand side of the cross product operation.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*
* @example
* // Creates
* // [0.0, -9.0, 8.0]
* // [9.0, 0.0, -7.0]
* // [-8.0, 7.0, 0.0]
* var m = Cesium.Matrix3.fromCrossProduct(new Cesium.Cartesian3(7.0, 8.0, 9.0));
*/
Matrix3.fromCrossProduct = function(vector, result) {
if (!defined(vector)) {
throw new DeveloperError('vector is required.');
}
if (!defined(result)) {
return new Matrix3(
0.0, -vector.z, vector.y,
vector.z, 0.0, -vector.x,
-vector.y, vector.x, 0.0);
}
result[0] = 0.0;
result[1] = vector.z;
result[2] = -vector.y;
result[3] = -vector.z;
result[4] = 0.0;
result[5] = vector.x;
result[6] = vector.y;
result[7] = -vector.x;
result[8] = 0.0;
return result;
};
/**
* Creates a rotation matrix around the x-axis.
*
* @param {Number} angle The angle, in radians, of the rotation. Positive angles are counterclockwise.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*
* @example
* // Rotate a point 45 degrees counterclockwise around the x-axis.
* var p = new Cesium.Cartesian3(5, 6, 7);
* var m = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(45.0));
* var rotated = Cesium.Matrix3.multiplyByVector(m, p, new Cesium.Cartesian3());
*/
Matrix3.fromRotationX = function(angle, result) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
var cosAngle = Math.cos(angle);
var sinAngle = Math.sin(angle);
if (!defined(result)) {
return new Matrix3(
1.0, 0.0, 0.0,
0.0, cosAngle, -sinAngle,
0.0, sinAngle, cosAngle);
}
result[0] = 1.0;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = cosAngle;
result[5] = sinAngle;
result[6] = 0.0;
result[7] = -sinAngle;
result[8] = cosAngle;
return result;
};
/**
* Creates a rotation matrix around the y-axis.
*
* @param {Number} angle The angle, in radians, of the rotation. Positive angles are counterclockwise.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*
* @example
* // Rotate a point 45 degrees counterclockwise around the y-axis.
* var p = new Cesium.Cartesian3(5, 6, 7);
* var m = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(45.0));
* var rotated = Cesium.Matrix3.multiplyByVector(m, p, new Cesium.Cartesian3());
*/
Matrix3.fromRotationY = function(angle, result) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
var cosAngle = Math.cos(angle);
var sinAngle = Math.sin(angle);
if (!defined(result)) {
return new Matrix3(
cosAngle, 0.0, sinAngle,
0.0, 1.0, 0.0,
-sinAngle, 0.0, cosAngle);
}
result[0] = cosAngle;
result[1] = 0.0;
result[2] = -sinAngle;
result[3] = 0.0;
result[4] = 1.0;
result[5] = 0.0;
result[6] = sinAngle;
result[7] = 0.0;
result[8] = cosAngle;
return result;
};
/**
* Creates a rotation matrix around the z-axis.
*
* @param {Number} angle The angle, in radians, of the rotation. Positive angles are counterclockwise.
* @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
*
* @example
* // Rotate a point 45 degrees counterclockwise around the z-axis.
* var p = new Cesium.Cartesian3(5, 6, 7);
* var m = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(45.0));
* var rotated = Cesium.Matrix3.multiplyByVector(m, p, new Cesium.Cartesian3());
*/
Matrix3.fromRotationZ = function(angle, result) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
var cosAngle = Math.cos(angle);
var sinAngle = Math.sin(angle);
if (!defined(result)) {
return new Matrix3(
cosAngle, -sinAngle, 0.0,
sinAngle, cosAngle, 0.0,
0.0, 0.0, 1.0);
}
result[0] = cosAngle;
result[1] = sinAngle;
result[2] = 0.0;
result[3] = -sinAngle;
result[4] = cosAngle;
result[5] = 0.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 1.0;
return result;
};
/**
* Creates an Array from the provided Matrix3 instance.
* The array will be in column-major order.
*
* @param {Matrix3} matrix The matrix to use..
* @param {Number[]} [result] The Array onto which to store the result.
* @returns {Number[]} The modified Array parameter or a new Array instance if one was not provided.
*/
Matrix3.toArray = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
return [matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6], matrix[7], matrix[8]];
}
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[3];
result[4] = matrix[4];
result[5] = matrix[5];
result[6] = matrix[6];
result[7] = matrix[7];
result[8] = matrix[8];
return result;
};
/**
* Computes the array index of the element at the provided row and column.
*
* @param {Number} row The zero-based index of the row.
* @param {Number} column The zero-based index of the column.
* @returns {Number} The index of the element at the provided row and column.
*
* @exception {DeveloperError} row must be 0, 1, or 2.
* @exception {DeveloperError} column must be 0, 1, or 2.
*
* @example
* var myMatrix = new Cesium.Matrix3();
* var column1Row0Index = Cesium.Matrix3.getElementIndex(1, 0);
* var column1Row0 = myMatrix[column1Row0Index]
* myMatrix[column1Row0Index] = 10.0;
*/
Matrix3.getElementIndex = function(column, row) {
if (typeof row !== 'number' || row < 0 || row > 2) {
throw new DeveloperError('row must be 0, 1, or 2.');
}
if (typeof column !== 'number' || column < 0 || column > 2) {
throw new DeveloperError('column must be 0, 1, or 2.');
}
return column * 3 + row;
};
/**
* Retrieves a copy of the matrix column at the provided index as a Cartesian3 instance.
*
* @param {Matrix3} matrix The matrix to use.
* @param {Number} index The zero-based index of the column to retrieve.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, or 2.
*/
Matrix3.getColumn = function(matrix, index, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (typeof index !== 'number' || index < 0 || index > 2) {
throw new DeveloperError('index must be 0, 1, or 2.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var startIndex = index * 3;
var x = matrix[startIndex];
var y = matrix[startIndex + 1];
var z = matrix[startIndex + 2];
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian3 instance.
*
* @param {Matrix3} matrix The matrix to use.
* @param {Number} index The zero-based index of the column to set.
* @param {Cartesian3} cartesian The Cartesian whose values will be assigned to the specified column.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, or 2.
*/
Matrix3.setColumn = function(matrix, index, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof index !== 'number' || index < 0 || index > 2) {
throw new DeveloperError('index must be 0, 1, or 2.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result = Matrix3.clone(matrix, result);
var startIndex = index * 3;
result[startIndex] = cartesian.x;
result[startIndex + 1] = cartesian.y;
result[startIndex + 2] = cartesian.z;
return result;
};
/**
* Retrieves a copy of the matrix row at the provided index as a Cartesian3 instance.
*
* @param {Matrix3} matrix The matrix to use.
* @param {Number} index The zero-based index of the row to retrieve.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, or 2.
*/
Matrix3.getRow = function(matrix, index, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (typeof index !== 'number' || index < 0 || index > 2) {
throw new DeveloperError('index must be 0, 1, or 2.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var x = matrix[index];
var y = matrix[index + 3];
var z = matrix[index + 6];
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian3 instance.
*
* @param {Matrix3} matrix The matrix to use.
* @param {Number} index The zero-based index of the row to set.
* @param {Cartesian3} cartesian The Cartesian whose values will be assigned to the specified row.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, or 2.
*/
Matrix3.setRow = function(matrix, index, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof index !== 'number' || index < 0 || index > 2) {
throw new DeveloperError('index must be 0, 1, or 2.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result = Matrix3.clone(matrix, result);
result[index] = cartesian.x;
result[index + 3] = cartesian.y;
result[index + 6] = cartesian.z;
return result;
};
var scratchColumn = new Cartesian3();
/**
* Extracts the non-uniform scale assuming the matrix is an affine transformation.
*
* @param {Matrix3} matrix The matrix.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Matrix3.getScale = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = Cartesian3.magnitude(Cartesian3.fromElements(matrix[0], matrix[1], matrix[2], scratchColumn));
result.y = Cartesian3.magnitude(Cartesian3.fromElements(matrix[3], matrix[4], matrix[5], scratchColumn));
result.z = Cartesian3.magnitude(Cartesian3.fromElements(matrix[6], matrix[7], matrix[8], scratchColumn));
return result;
};
var scratchScale = new Cartesian3();
/**
* Computes the maximum scale assuming the matrix is an affine transformation.
* The maximum scale is the maximum length of the column vectors.
*
* @param {Matrix3} matrix The matrix.
* @returns {Number} The maximum scale.
*/
Matrix3.getMaximumScale = function(matrix) {
Matrix3.getScale(matrix, scratchScale);
return Cartesian3.maximumComponent(scratchScale);
};
/**
* Computes the product of two matrices.
*
* @param {Matrix3} left The first matrix.
* @param {Matrix3} right The second matrix.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.multiply = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var column0Row0 = left[0] * right[0] + left[3] * right[1] + left[6] * right[2];
var column0Row1 = left[1] * right[0] + left[4] * right[1] + left[7] * right[2];
var column0Row2 = left[2] * right[0] + left[5] * right[1] + left[8] * right[2];
var column1Row0 = left[0] * right[3] + left[3] * right[4] + left[6] * right[5];
var column1Row1 = left[1] * right[3] + left[4] * right[4] + left[7] * right[5];
var column1Row2 = left[2] * right[3] + left[5] * right[4] + left[8] * right[5];
var column2Row0 = left[0] * right[6] + left[3] * right[7] + left[6] * right[8];
var column2Row1 = left[1] * right[6] + left[4] * right[7] + left[7] * right[8];
var column2Row2 = left[2] * right[6] + left[5] * right[7] + left[8] * right[8];
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column0Row2;
result[3] = column1Row0;
result[4] = column1Row1;
result[5] = column1Row2;
result[6] = column2Row0;
result[7] = column2Row1;
result[8] = column2Row2;
return result;
};
/**
* Computes the sum of two matrices.
*
* @param {Matrix3} left The first matrix.
* @param {Matrix3} right The second matrix.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = left[0] + right[0];
result[1] = left[1] + right[1];
result[2] = left[2] + right[2];
result[3] = left[3] + right[3];
result[4] = left[4] + right[4];
result[5] = left[5] + right[5];
result[6] = left[6] + right[6];
result[7] = left[7] + right[7];
result[8] = left[8] + right[8];
return result;
};
/**
* Computes the difference of two matrices.
*
* @param {Matrix3} left The first matrix.
* @param {Matrix3} right The second matrix.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = left[0] - right[0];
result[1] = left[1] - right[1];
result[2] = left[2] - right[2];
result[3] = left[3] - right[3];
result[4] = left[4] - right[4];
result[5] = left[5] - right[5];
result[6] = left[6] - right[6];
result[7] = left[7] - right[7];
result[8] = left[8] - right[8];
return result;
};
/**
* Computes the product of a matrix and a column vector.
*
* @param {Matrix3} matrix The matrix.
* @param {Cartesian3} cartesian The column.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Matrix3.multiplyByVector = function(matrix, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var vX = cartesian.x;
var vY = cartesian.y;
var vZ = cartesian.z;
var x = matrix[0] * vX + matrix[3] * vY + matrix[6] * vZ;
var y = matrix[1] * vX + matrix[4] * vY + matrix[7] * vZ;
var z = matrix[2] * vX + matrix[5] * vY + matrix[8] * vZ;
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Computes the product of a matrix and a scalar.
*
* @param {Matrix3} matrix The matrix.
* @param {Number} scalar The number to multiply by.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.multiplyByScalar = function(matrix, scalar, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar must be a number');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0] * scalar;
result[1] = matrix[1] * scalar;
result[2] = matrix[2] * scalar;
result[3] = matrix[3] * scalar;
result[4] = matrix[4] * scalar;
result[5] = matrix[5] * scalar;
result[6] = matrix[6] * scalar;
result[7] = matrix[7] * scalar;
result[8] = matrix[8] * scalar;
return result;
};
/**
* Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix.
*
* @param {Matrix3} matrix The matrix on the left-hand side.
* @param {Cartesian3} scale The non-uniform scale on the right-hand side.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*
*
* @example
* // Instead of Cesium.Matrix3.multiply(m, Cesium.Matrix3.fromScale(scale), m);
* Cesium.Matrix3.multiplyByScale(m, scale, m);
*
* @see Matrix3.fromScale
* @see Matrix3.multiplyByUniformScale
*/
Matrix3.multiplyByScale = function(matrix, scale, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(scale)) {
throw new DeveloperError('scale is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0] * scale.x;
result[1] = matrix[1] * scale.x;
result[2] = matrix[2] * scale.x;
result[3] = matrix[3] * scale.y;
result[4] = matrix[4] * scale.y;
result[5] = matrix[5] * scale.y;
result[6] = matrix[6] * scale.z;
result[7] = matrix[7] * scale.z;
result[8] = matrix[8] * scale.z;
return result;
};
/**
* Creates a negated copy of the provided matrix.
*
* @param {Matrix3} matrix The matrix to negate.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.negate = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = -matrix[0];
result[1] = -matrix[1];
result[2] = -matrix[2];
result[3] = -matrix[3];
result[4] = -matrix[4];
result[5] = -matrix[5];
result[6] = -matrix[6];
result[7] = -matrix[7];
result[8] = -matrix[8];
return result;
};
/**
* Computes the transpose of the provided matrix.
*
* @param {Matrix3} matrix The matrix to transpose.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.transpose = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var column0Row0 = matrix[0];
var column0Row1 = matrix[3];
var column0Row2 = matrix[6];
var column1Row0 = matrix[1];
var column1Row1 = matrix[4];
var column1Row2 = matrix[7];
var column2Row0 = matrix[2];
var column2Row1 = matrix[5];
var column2Row2 = matrix[8];
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column0Row2;
result[3] = column1Row0;
result[4] = column1Row1;
result[5] = column1Row2;
result[6] = column2Row0;
result[7] = column2Row1;
result[8] = column2Row2;
return result;
};
function computeFrobeniusNorm(matrix) {
var norm = 0.0;
for (var i = 0; i < 9; ++i) {
var temp = matrix[i];
norm += temp * temp;
}
return Math.sqrt(norm);
}
var rowVal = [1, 0, 0];
var colVal = [2, 2, 1];
function offDiagonalFrobeniusNorm(matrix) {
// Computes the "off-diagonal" Frobenius norm.
// Assumes matrix is symmetric.
var norm = 0.0;
for (var i = 0; i < 3; ++i) {
var temp = matrix[Matrix3.getElementIndex(colVal[i], rowVal[i])];
norm += 2.0 * temp * temp;
}
return Math.sqrt(norm);
}
function shurDecomposition(matrix, result) {
// This routine was created based upon Matrix Computations, 3rd ed., by Golub and Van Loan,
// section 8.4.2 The 2by2 Symmetric Schur Decomposition.
//
// The routine takes a matrix, which is assumed to be symmetric, and
// finds the largest off-diagonal term, and then creates
// a matrix (result) which can be used to help reduce it
var tolerance = CesiumMath.EPSILON15;
var maxDiagonal = 0.0;
var rotAxis = 1;
// find pivot (rotAxis) based on max diagonal of matrix
for (var i = 0; i < 3; ++i) {
var temp = Math.abs(matrix[Matrix3.getElementIndex(colVal[i], rowVal[i])]);
if (temp > maxDiagonal) {
rotAxis = i;
maxDiagonal = temp;
}
}
var c = 1.0;
var s = 0.0;
var p = rowVal[rotAxis];
var q = colVal[rotAxis];
if (Math.abs(matrix[Matrix3.getElementIndex(q, p)]) > tolerance) {
var qq = matrix[Matrix3.getElementIndex(q, q)];
var pp = matrix[Matrix3.getElementIndex(p, p)];
var qp = matrix[Matrix3.getElementIndex(q, p)];
var tau = (qq - pp) / 2.0 / qp;
var t;
if (tau < 0.0) {
t = -1.0 / (-tau + Math.sqrt(1.0 + tau * tau));
} else {
t = 1.0 / (tau + Math.sqrt(1.0 + tau * tau));
}
c = 1.0 / Math.sqrt(1.0 + t * t);
s = t * c;
}
result = Matrix3.clone(Matrix3.IDENTITY, result);
result[Matrix3.getElementIndex(p, p)] = result[Matrix3.getElementIndex(q, q)] = c;
result[Matrix3.getElementIndex(q, p)] = s;
result[Matrix3.getElementIndex(p, q)] = -s;
return result;
}
var jMatrix = new Matrix3();
var jMatrixTranspose = new Matrix3();
/**
* Computes the eigenvectors and eigenvalues of a symmetric matrix.
*
* Returns a diagonal matrix and unitary matrix such that:
* matrix = unitary matrix * diagonal matrix * transpose(unitary matrix)
*
*
* The values along the diagonal of the diagonal matrix are the eigenvalues. The columns
* of the unitary matrix are the corresponding eigenvectors.
*
*
* @param {Matrix3} matrix The matrix to decompose into diagonal and unitary matrix. Expected to be symmetric.
* @param {Object} [result] An object with unitary and diagonal properties which are matrices onto which to store the result.
* @returns {Object} An object with unitary and diagonal properties which are the unitary and diagonal matrices, respectively.
*
* @example
* var a = //... symetric matrix
* var result = {
* unitary : new Cesium.Matrix3(),
* diagonal : new Cesium.Matrix3()
* };
* Cesium.Matrix3.computeEigenDecomposition(a, result);
*
* var unitaryTranspose = Cesium.Matrix3.transpose(result.unitary, new Cesium.Matrix3());
* var b = Cesium.Matrix3.multiply(result.unitary, result.diagonal, new Cesium.Matrix3());
* Cesium.Matrix3.multiply(b, unitaryTranspose, b); // b is now equal to a
*
* var lambda = Cesium.Matrix3.getColumn(result.diagonal, 0, new Cesium.Cartesian3()).x; // first eigenvalue
* var v = Cesium.Matrix3.getColumn(result.unitary, 0, new Cesium.Cartesian3()); // first eigenvector
* var c = Cesium.Cartesian3.multiplyByScalar(v, lambda, new Cesium.Cartesian3()); // equal to Cesium.Matrix3.multiplyByVector(a, v)
*/
Matrix3.computeEigenDecomposition = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
// This routine was created based upon Matrix Computations, 3rd ed., by Golub and Van Loan,
// section 8.4.3 The Classical Jacobi Algorithm
var tolerance = CesiumMath.EPSILON20;
var maxSweeps = 10;
var count = 0;
var sweep = 0;
if (!defined(result)) {
result = {};
}
var unitaryMatrix = result.unitary = Matrix3.clone(Matrix3.IDENTITY, result.unitary);
var diagMatrix = result.diagonal = Matrix3.clone(matrix, result.diagonal);
var epsilon = tolerance * computeFrobeniusNorm(diagMatrix);
while (sweep < maxSweeps && offDiagonalFrobeniusNorm(diagMatrix) > epsilon) {
shurDecomposition(diagMatrix, jMatrix);
Matrix3.transpose(jMatrix, jMatrixTranspose);
Matrix3.multiply(diagMatrix, jMatrix, diagMatrix);
Matrix3.multiply(jMatrixTranspose, diagMatrix, diagMatrix);
Matrix3.multiply(unitaryMatrix, jMatrix, unitaryMatrix);
if (++count > 2) {
++sweep;
count = 0;
}
}
return result;
};
/**
* Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
*
* @param {Matrix3} matrix The matrix with signed elements.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
Matrix3.abs = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = Math.abs(matrix[0]);
result[1] = Math.abs(matrix[1]);
result[2] = Math.abs(matrix[2]);
result[3] = Math.abs(matrix[3]);
result[4] = Math.abs(matrix[4]);
result[5] = Math.abs(matrix[5]);
result[6] = Math.abs(matrix[6]);
result[7] = Math.abs(matrix[7]);
result[8] = Math.abs(matrix[8]);
return result;
};
/**
* Computes the determinant of the provided matrix.
*
* @param {Matrix3} matrix The matrix to use.
* @returns {Number} The value of the determinant of the matrix.
*/
Matrix3.determinant = function(matrix) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
var m11 = matrix[0];
var m21 = matrix[3];
var m31 = matrix[6];
var m12 = matrix[1];
var m22 = matrix[4];
var m32 = matrix[7];
var m13 = matrix[2];
var m23 = matrix[5];
var m33 = matrix[8];
return m11 * (m22 * m33 - m23 * m32) + m12 * (m23 * m31 - m21 * m33) + m13 * (m21 * m32 - m22 * m31);
};
/**
* Computes the inverse of the provided matrix.
*
* @param {Matrix3} matrix The matrix to invert.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*
* @exception {DeveloperError} matrix is not invertible.
*/
Matrix3.inverse = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var m11 = matrix[0];
var m21 = matrix[1];
var m31 = matrix[2];
var m12 = matrix[3];
var m22 = matrix[4];
var m32 = matrix[5];
var m13 = matrix[6];
var m23 = matrix[7];
var m33 = matrix[8];
var determinant = Matrix3.determinant(matrix);
if (Math.abs(determinant) <= CesiumMath.EPSILON15) {
throw new DeveloperError('matrix is not invertible');
}
result[0] = m22 * m33 - m23 * m32;
result[1] = m23 * m31 - m21 * m33;
result[2] = m21 * m32 - m22 * m31;
result[3] = m13 * m32 - m12 * m33;
result[4] = m11 * m33 - m13 * m31;
result[5] = m12 * m31 - m11 * m32;
result[6] = m12 * m23 - m13 * m22;
result[7] = m13 * m21 - m11 * m23;
result[8] = m11 * m22 - m12 * m21;
var scale = 1.0 / determinant;
return Matrix3.multiplyByScalar(result, scale, result);
};
/**
* Compares the provided matrices componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Matrix3} [left] The first matrix.
* @param {Matrix3} [right] The second matrix.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Matrix3.equals = function(left, right) {
return (left === right) ||
(defined(left) &&
defined(right) &&
left[0] === right[0] &&
left[1] === right[1] &&
left[2] === right[2] &&
left[3] === right[3] &&
left[4] === right[4] &&
left[5] === right[5] &&
left[6] === right[6] &&
left[7] === right[7] &&
left[8] === right[8]);
};
/**
* Compares the provided matrices componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Matrix3} [left] The first matrix.
* @param {Matrix3} [right] The second matrix.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Matrix3.equalsEpsilon = function(left, right, epsilon) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon must be a number');
}
return (left === right) ||
(defined(left) &&
defined(right) &&
Math.abs(left[0] - right[0]) <= epsilon &&
Math.abs(left[1] - right[1]) <= epsilon &&
Math.abs(left[2] - right[2]) <= epsilon &&
Math.abs(left[3] - right[3]) <= epsilon &&
Math.abs(left[4] - right[4]) <= epsilon &&
Math.abs(left[5] - right[5]) <= epsilon &&
Math.abs(left[6] - right[6]) <= epsilon &&
Math.abs(left[7] - right[7]) <= epsilon &&
Math.abs(left[8] - right[8]) <= epsilon);
};
/**
* An immutable Matrix3 instance initialized to the identity matrix.
*
* @type {Matrix3}
* @constant
*/
Matrix3.IDENTITY = freezeObject(new Matrix3(1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0));
/**
* An immutable Matrix3 instance initialized to the zero matrix.
*
* @type {Matrix3}
* @constant
*/
Matrix3.ZERO = freezeObject(new Matrix3(0.0, 0.0, 0.0,
0.0, 0.0, 0.0,
0.0, 0.0, 0.0));
/**
* The index into Matrix3 for column 0, row 0.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN0ROW0 = 0;
/**
* The index into Matrix3 for column 0, row 1.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN0ROW1 = 1;
/**
* The index into Matrix3 for column 0, row 2.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN0ROW2 = 2;
/**
* The index into Matrix3 for column 1, row 0.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN1ROW0 = 3;
/**
* The index into Matrix3 for column 1, row 1.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN1ROW1 = 4;
/**
* The index into Matrix3 for column 1, row 2.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN1ROW2 = 5;
/**
* The index into Matrix3 for column 2, row 0.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN2ROW0 = 6;
/**
* The index into Matrix3 for column 2, row 1.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN2ROW1 = 7;
/**
* The index into Matrix3 for column 2, row 2.
*
* @type {Number}
* @constant
*/
Matrix3.COLUMN2ROW2 = 8;
defineProperties(Matrix3.prototype, {
/**
* Gets the number of items in the collection.
* @memberof Matrix3.prototype
*
* @type {Number}
*/
length : {
get : function() {
return Matrix3.packedLength;
}
}
});
/**
* Duplicates the provided Matrix3 instance.
*
* @param {Matrix3} [result] The object onto which to store the result.
* @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided.
*/
Matrix3.prototype.clone = function(result) {
return Matrix3.clone(this, result);
};
/**
* Compares this matrix to the provided matrix componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Matrix3} [right] The right hand side matrix.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Matrix3.prototype.equals = function(right) {
return Matrix3.equals(this, right);
};
/**
* @private
*/
Matrix3.equalsArray = function(matrix, array, offset) {
return matrix[0] === array[offset] &&
matrix[1] === array[offset + 1] &&
matrix[2] === array[offset + 2] &&
matrix[3] === array[offset + 3] &&
matrix[4] === array[offset + 4] &&
matrix[5] === array[offset + 5] &&
matrix[6] === array[offset + 6] &&
matrix[7] === array[offset + 7] &&
matrix[8] === array[offset + 8];
};
/**
* Compares this matrix to the provided matrix componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Matrix3} [right] The right hand side matrix.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
Matrix3.prototype.equalsEpsilon = function(right, epsilon) {
return Matrix3.equalsEpsilon(this, right, epsilon);
};
/**
* Creates a string representing this Matrix with each row being
* on a separate line and in the format '(column0, column1, column2)'.
*
* @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1, column2)'.
*/
Matrix3.prototype.toString = function() {
return '(' + this[0] + ', ' + this[3] + ', ' + this[6] + ')\n' +
'(' + this[1] + ', ' + this[4] + ', ' + this[7] + ')\n' +
'(' + this[2] + ', ' + this[5] + ', ' + this[8] + ')';
};
return Matrix3;
});
/*global define*/
define('Core/Cartesian4',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./freezeObject',
'./Math'
], function(
defaultValue,
defined,
DeveloperError,
freezeObject,
CesiumMath) {
'use strict';
/**
* A 4D Cartesian point.
* @alias Cartesian4
* @constructor
*
* @param {Number} [x=0.0] The X component.
* @param {Number} [y=0.0] The Y component.
* @param {Number} [z=0.0] The Z component.
* @param {Number} [w=0.0] The W component.
*
* @see Cartesian2
* @see Cartesian3
* @see Packable
*/
function Cartesian4(x, y, z, w) {
/**
* The X component.
* @type {Number}
* @default 0.0
*/
this.x = defaultValue(x, 0.0);
/**
* The Y component.
* @type {Number}
* @default 0.0
*/
this.y = defaultValue(y, 0.0);
/**
* The Z component.
* @type {Number}
* @default 0.0
*/
this.z = defaultValue(z, 0.0);
/**
* The W component.
* @type {Number}
* @default 0.0
*/
this.w = defaultValue(w, 0.0);
}
/**
* Creates a Cartesian4 instance from x, y, z and w coordinates.
*
* @param {Number} x The x coordinate.
* @param {Number} y The y coordinate.
* @param {Number} z The z coordinate.
* @param {Number} w The w coordinate.
* @param {Cartesian4} [result] The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
*/
Cartesian4.fromElements = function(x, y, z, w, result) {
if (!defined(result)) {
return new Cartesian4(x, y, z, w);
}
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
/**
* Creates a Cartesian4 instance from a {@link Color}. red
, green
, blue
,
* and alpha
map to x
, y
, z
, and w
, respectively.
*
* @param {Color} color The source color.
* @param {Cartesian4} [result] The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
*/
Cartesian4.fromColor = function(color, result) {
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(result)) {
return new Cartesian4(color.red, color.green, color.blue, color.alpha);
}
result.x = color.red;
result.y = color.green;
result.z = color.blue;
result.w = color.alpha;
return result;
};
/**
* Duplicates a Cartesian4 instance.
*
* @param {Cartesian4} cartesian The Cartesian to duplicate.
* @param {Cartesian4} [result] The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided. (Returns undefined if cartesian is undefined)
*/
Cartesian4.clone = function(cartesian, result) {
if (!defined(cartesian)) {
return undefined;
}
if (!defined(result)) {
return new Cartesian4(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
}
result.x = cartesian.x;
result.y = cartesian.y;
result.z = cartesian.z;
result.w = cartesian.w;
return result;
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Cartesian4.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {Cartesian4} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Cartesian4.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.x;
array[startingIndex++] = value.y;
array[startingIndex++] = value.z;
array[startingIndex] = value.w;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Cartesian4} [result] The object into which to store the result.
* @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
*/
Cartesian4.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Cartesian4();
}
result.x = array[startingIndex++];
result.y = array[startingIndex++];
result.z = array[startingIndex++];
result.w = array[startingIndex];
return result;
};
/**
* Flattens an array of Cartesian4s into and array of components.
*
* @param {Cartesian4[]} array The array of cartesians to pack.
* @param {Number[]} result The array onto which to store the result.
* @returns {Number[]} The packed array.
*/
Cartesian4.packArray = function(array, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
var length = array.length;
if (!defined(result)) {
result = new Array(length * 4);
} else {
result.length = length * 4;
}
for (var i = 0; i < length; ++i) {
Cartesian4.pack(array[i], result, i * 4);
}
return result;
};
/**
* Unpacks an array of cartesian components into and array of Cartesian4s.
*
* @param {Number[]} array The array of components to unpack.
* @param {Cartesian4[]} result The array onto which to store the result.
* @returns {Cartesian4[]} The unpacked array.
*/
Cartesian4.unpackArray = function(array, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
var length = array.length;
if (!defined(result)) {
result = new Array(length / 4);
} else {
result.length = length / 4;
}
for (var i = 0; i < length; i += 4) {
var index = i / 4;
result[index] = Cartesian4.unpack(array, i, result[index]);
}
return result;
};
/**
* Creates a Cartesian4 from four consecutive elements in an array.
* @function
*
* @param {Number[]} array The array whose four consecutive elements correspond to the x, y, z, and w components, respectively.
* @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to the x component.
* @param {Cartesian4} [result] The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
*
* @example
* // Create a Cartesian4 with (1.0, 2.0, 3.0, 4.0)
* var v = [1.0, 2.0, 3.0, 4.0];
* var p = Cesium.Cartesian4.fromArray(v);
*
* // Create a Cartesian4 with (1.0, 2.0, 3.0, 4.0) using an offset into an array
* var v2 = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0];
* var p2 = Cesium.Cartesian4.fromArray(v2, 2);
*/
Cartesian4.fromArray = Cartesian4.unpack;
/**
* Computes the value of the maximum component for the supplied Cartesian.
*
* @param {Cartesian4} cartesian The cartesian to use.
* @returns {Number} The value of the maximum component.
*/
Cartesian4.maximumComponent = function(cartesian) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
return Math.max(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
};
/**
* Computes the value of the minimum component for the supplied Cartesian.
*
* @param {Cartesian4} cartesian The cartesian to use.
* @returns {Number} The value of the minimum component.
*/
Cartesian4.minimumComponent = function(cartesian) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
return Math.min(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
};
/**
* Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
*
* @param {Cartesian4} first A cartesian to compare.
* @param {Cartesian4} second A cartesian to compare.
* @param {Cartesian4} result The object into which to store the result.
* @returns {Cartesian4} A cartesian with the minimum components.
*/
Cartesian4.minimumByComponent = function(first, second, result) {
if (!defined(first)) {
throw new DeveloperError('first is required.');
}
if (!defined(second)) {
throw new DeveloperError('second is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
result.x = Math.min(first.x, second.x);
result.y = Math.min(first.y, second.y);
result.z = Math.min(first.z, second.z);
result.w = Math.min(first.w, second.w);
return result;
};
/**
* Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
*
* @param {Cartesian4} first A cartesian to compare.
* @param {Cartesian4} second A cartesian to compare.
* @param {Cartesian4} result The object into which to store the result.
* @returns {Cartesian4} A cartesian with the maximum components.
*/
Cartesian4.maximumByComponent = function(first, second, result) {
if (!defined(first)) {
throw new DeveloperError('first is required.');
}
if (!defined(second)) {
throw new DeveloperError('second is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
result.x = Math.max(first.x, second.x);
result.y = Math.max(first.y, second.y);
result.z = Math.max(first.z, second.z);
result.w = Math.max(first.w, second.w);
return result;
};
/**
* Computes the provided Cartesian's squared magnitude.
*
* @param {Cartesian4} cartesian The Cartesian instance whose squared magnitude is to be computed.
* @returns {Number} The squared magnitude.
*/
Cartesian4.magnitudeSquared = function(cartesian) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
return cartesian.x * cartesian.x + cartesian.y * cartesian.y + cartesian.z * cartesian.z + cartesian.w * cartesian.w;
};
/**
* Computes the Cartesian's magnitude (length).
*
* @param {Cartesian4} cartesian The Cartesian instance whose magnitude is to be computed.
* @returns {Number} The magnitude.
*/
Cartesian4.magnitude = function(cartesian) {
return Math.sqrt(Cartesian4.magnitudeSquared(cartesian));
};
var distanceScratch = new Cartesian4();
/**
* Computes the 4-space distance between two points.
*
* @param {Cartesian4} left The first point to compute the distance from.
* @param {Cartesian4} right The second point to compute the distance to.
* @returns {Number} The distance between two points.
*
* @example
* // Returns 1.0
* var d = Cesium.Cartesian4.distance(
* new Cesium.Cartesian4(1.0, 0.0, 0.0, 0.0),
* new Cesium.Cartesian4(2.0, 0.0, 0.0, 0.0));
*/
Cartesian4.distance = function(left, right) {
if (!defined(left) || !defined(right)) {
throw new DeveloperError('left and right are required.');
}
Cartesian4.subtract(left, right, distanceScratch);
return Cartesian4.magnitude(distanceScratch);
};
/**
* Computes the squared distance between two points. Comparing squared distances
* using this function is more efficient than comparing distances using {@link Cartesian4#distance}.
*
* @param {Cartesian4} left The first point to compute the distance from.
* @param {Cartesian4} right The second point to compute the distance to.
* @returns {Number} The distance between two points.
*
* @example
* // Returns 4.0, not 2.0
* var d = Cesium.Cartesian4.distance(
* new Cesium.Cartesian4(1.0, 0.0, 0.0, 0.0),
* new Cesium.Cartesian4(3.0, 0.0, 0.0, 0.0));
*/
Cartesian4.distanceSquared = function(left, right) {
if (!defined(left) || !defined(right)) {
throw new DeveloperError('left and right are required.');
}
Cartesian4.subtract(left, right, distanceScratch);
return Cartesian4.magnitudeSquared(distanceScratch);
};
/**
* Computes the normalized form of the supplied Cartesian.
*
* @param {Cartesian4} cartesian The Cartesian to be normalized.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.normalize = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var magnitude = Cartesian4.magnitude(cartesian);
result.x = cartesian.x / magnitude;
result.y = cartesian.y / magnitude;
result.z = cartesian.z / magnitude;
result.w = cartesian.w / magnitude;
if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z) || isNaN(result.w)) {
throw new DeveloperError('normalized result is not a number');
}
return result;
};
/**
* Computes the dot (scalar) product of two Cartesians.
*
* @param {Cartesian4} left The first Cartesian.
* @param {Cartesian4} right The second Cartesian.
* @returns {Number} The dot product.
*/
Cartesian4.dot = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
return left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w;
};
/**
* Computes the componentwise product of two Cartesians.
*
* @param {Cartesian4} left The first Cartesian.
* @param {Cartesian4} right The second Cartesian.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.multiplyComponents = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x * right.x;
result.y = left.y * right.y;
result.z = left.z * right.z;
result.w = left.w * right.w;
return result;
};
/**
* Computes the componentwise quotient of two Cartesians.
*
* @param {Cartesian4} left The first Cartesian.
* @param {Cartesian4} right The second Cartesian.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.divideComponents = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x / right.x;
result.y = left.y / right.y;
result.z = left.z / right.z;
result.w = left.w / right.w;
return result;
};
/**
* Computes the componentwise sum of two Cartesians.
*
* @param {Cartesian4} left The first Cartesian.
* @param {Cartesian4} right The second Cartesian.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x + right.x;
result.y = left.y + right.y;
result.z = left.z + right.z;
result.w = left.w + right.w;
return result;
};
/**
* Computes the componentwise difference of two Cartesians.
*
* @param {Cartesian4} left The first Cartesian.
* @param {Cartesian4} right The second Cartesian.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x - right.x;
result.y = left.y - right.y;
result.z = left.z - right.z;
result.w = left.w - right.w;
return result;
};
/**
* Multiplies the provided Cartesian componentwise by the provided scalar.
*
* @param {Cartesian4} cartesian The Cartesian to be scaled.
* @param {Number} scalar The scalar to multiply with.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.multiplyByScalar = function(cartesian, scalar, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = cartesian.x * scalar;
result.y = cartesian.y * scalar;
result.z = cartesian.z * scalar;
result.w = cartesian.w * scalar;
return result;
};
/**
* Divides the provided Cartesian componentwise by the provided scalar.
*
* @param {Cartesian4} cartesian The Cartesian to be divided.
* @param {Number} scalar The scalar to divide by.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.divideByScalar = function(cartesian, scalar, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = cartesian.x / scalar;
result.y = cartesian.y / scalar;
result.z = cartesian.z / scalar;
result.w = cartesian.w / scalar;
return result;
};
/**
* Negates the provided Cartesian.
*
* @param {Cartesian4} cartesian The Cartesian to be negated.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.negate = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = -cartesian.x;
result.y = -cartesian.y;
result.z = -cartesian.z;
result.w = -cartesian.w;
return result;
};
/**
* Computes the absolute value of the provided Cartesian.
*
* @param {Cartesian4} cartesian The Cartesian whose absolute value is to be computed.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.abs = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = Math.abs(cartesian.x);
result.y = Math.abs(cartesian.y);
result.z = Math.abs(cartesian.z);
result.w = Math.abs(cartesian.w);
return result;
};
var lerpScratch = new Cartesian4();
/**
* Computes the linear interpolation or extrapolation at t using the provided cartesians.
*
* @param {Cartesian4} start The value corresponding to t at 0.0.
* @param {Cartesian4}end The value corresponding to t at 1.0.
* @param {Number} t The point along t at which to interpolate.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Cartesian4.lerp = function(start, end, t, result) {
if (!defined(start)) {
throw new DeveloperError('start is required.');
}
if (!defined(end)) {
throw new DeveloperError('end is required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
Cartesian4.multiplyByScalar(end, t, lerpScratch);
result = Cartesian4.multiplyByScalar(start, 1.0 - t, result);
return Cartesian4.add(lerpScratch, result, result);
};
var mostOrthogonalAxisScratch = new Cartesian4();
/**
* Returns the axis that is most orthogonal to the provided Cartesian.
*
* @param {Cartesian4} cartesian The Cartesian on which to find the most orthogonal axis.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The most orthogonal axis.
*/
Cartesian4.mostOrthogonalAxis = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var f = Cartesian4.normalize(cartesian, mostOrthogonalAxisScratch);
Cartesian4.abs(f, f);
if (f.x <= f.y) {
if (f.x <= f.z) {
if (f.x <= f.w) {
result = Cartesian4.clone(Cartesian4.UNIT_X, result);
} else {
result = Cartesian4.clone(Cartesian4.UNIT_W, result);
}
} else if (f.z <= f.w) {
result = Cartesian4.clone(Cartesian4.UNIT_Z, result);
} else {
result = Cartesian4.clone(Cartesian4.UNIT_W, result);
}
} else if (f.y <= f.z) {
if (f.y <= f.w) {
result = Cartesian4.clone(Cartesian4.UNIT_Y, result);
} else {
result = Cartesian4.clone(Cartesian4.UNIT_W, result);
}
} else if (f.z <= f.w) {
result = Cartesian4.clone(Cartesian4.UNIT_Z, result);
} else {
result = Cartesian4.clone(Cartesian4.UNIT_W, result);
}
return result;
};
/**
* Compares the provided Cartesians componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartesian4} [left] The first Cartesian.
* @param {Cartesian4} [right] The second Cartesian.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Cartesian4.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.x === right.x) &&
(left.y === right.y) &&
(left.z === right.z) &&
(left.w === right.w));
};
/**
* @private
*/
Cartesian4.equalsArray = function(cartesian, array, offset) {
return cartesian.x === array[offset] &&
cartesian.y === array[offset + 1] &&
cartesian.z === array[offset + 2] &&
cartesian.w === array[offset + 3];
};
/**
* Compares the provided Cartesians componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {Cartesian4} [left] The first Cartesian.
* @param {Cartesian4} [right] The second Cartesian.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Cartesian4.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
return (left === right) ||
(defined(left) &&
defined(right) &&
CesiumMath.equalsEpsilon(left.x, right.x, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.y, right.y, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.z, right.z, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.w, right.w, relativeEpsilon, absoluteEpsilon));
};
/**
* An immutable Cartesian4 instance initialized to (0.0, 0.0, 0.0, 0.0).
*
* @type {Cartesian4}
* @constant
*/
Cartesian4.ZERO = freezeObject(new Cartesian4(0.0, 0.0, 0.0, 0.0));
/**
* An immutable Cartesian4 instance initialized to (1.0, 0.0, 0.0, 0.0).
*
* @type {Cartesian4}
* @constant
*/
Cartesian4.UNIT_X = freezeObject(new Cartesian4(1.0, 0.0, 0.0, 0.0));
/**
* An immutable Cartesian4 instance initialized to (0.0, 1.0, 0.0, 0.0).
*
* @type {Cartesian4}
* @constant
*/
Cartesian4.UNIT_Y = freezeObject(new Cartesian4(0.0, 1.0, 0.0, 0.0));
/**
* An immutable Cartesian4 instance initialized to (0.0, 0.0, 1.0, 0.0).
*
* @type {Cartesian4}
* @constant
*/
Cartesian4.UNIT_Z = freezeObject(new Cartesian4(0.0, 0.0, 1.0, 0.0));
/**
* An immutable Cartesian4 instance initialized to (0.0, 0.0, 0.0, 1.0).
*
* @type {Cartesian4}
* @constant
*/
Cartesian4.UNIT_W = freezeObject(new Cartesian4(0.0, 0.0, 0.0, 1.0));
/**
* Duplicates this Cartesian4 instance.
*
* @param {Cartesian4} [result] The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
*/
Cartesian4.prototype.clone = function(result) {
return Cartesian4.clone(this, result);
};
/**
* Compares this Cartesian against the provided Cartesian componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Cartesian4} [right] The right hand side Cartesian.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Cartesian4.prototype.equals = function(right) {
return Cartesian4.equals(this, right);
};
/**
* Compares this Cartesian against the provided Cartesian componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {Cartesian4} [right] The right hand side Cartesian.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
Cartesian4.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
return Cartesian4.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
};
/**
* Creates a string representing this Cartesian in the format '(x, y)'.
*
* @returns {String} A string representing the provided Cartesian in the format '(x, y)'.
*/
Cartesian4.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + ')';
};
return Cartesian4;
});
/*global define*/
define('Core/RuntimeError',[
'./defined'
], function(
defined) {
'use strict';
/**
* Constructs an exception object that is thrown due to an error that can occur at runtime, e.g.,
* out of memory, could not compile shader, etc. If a function may throw this
* exception, the calling code should be prepared to catch it.
*
* On the other hand, a {@link DeveloperError} indicates an exception due
* to a developer error, e.g., invalid argument, that usually indicates a bug in the
* calling code.
*
* @alias RuntimeError
* @constructor
* @extends Error
*
* @param {String} [message] The error message for this exception.
*
* @see DeveloperError
*/
function RuntimeError(message) {
/**
* 'RuntimeError' indicating that this exception was thrown due to a runtime error.
* @type {String}
* @readonly
*/
this.name = 'RuntimeError';
/**
* The explanation for why this exception was thrown.
* @type {String}
* @readonly
*/
this.message = message;
//Browsers such as IE don't have a stack property until you actually throw the error.
var stack;
try {
throw new Error();
} catch (e) {
stack = e.stack;
}
/**
* The stack trace of this exception, if available.
* @type {String}
* @readonly
*/
this.stack = stack;
}
if (defined(Object.create)) {
RuntimeError.prototype = Object.create(Error.prototype);
RuntimeError.prototype.constructor = RuntimeError;
}
RuntimeError.prototype.toString = function() {
var str = this.name + ': ' + this.message;
if (defined(this.stack)) {
str += '\n' + this.stack.toString();
}
return str;
};
return RuntimeError;
});
/*global define*/
define('Core/Matrix4',[
'./Cartesian3',
'./Cartesian4',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./freezeObject',
'./Math',
'./Matrix3',
'./RuntimeError'
], function(
Cartesian3,
Cartesian4,
defaultValue,
defined,
defineProperties,
DeveloperError,
freezeObject,
CesiumMath,
Matrix3,
RuntimeError) {
'use strict';
/**
* A 4x4 matrix, indexable as a column-major order array.
* Constructor parameters are in row-major order for code readability.
* @alias Matrix4
* @constructor
*
* @param {Number} [column0Row0=0.0] The value for column 0, row 0.
* @param {Number} [column1Row0=0.0] The value for column 1, row 0.
* @param {Number} [column2Row0=0.0] The value for column 2, row 0.
* @param {Number} [column3Row0=0.0] The value for column 3, row 0.
* @param {Number} [column0Row1=0.0] The value for column 0, row 1.
* @param {Number} [column1Row1=0.0] The value for column 1, row 1.
* @param {Number} [column2Row1=0.0] The value for column 2, row 1.
* @param {Number} [column3Row1=0.0] The value for column 3, row 1.
* @param {Number} [column0Row2=0.0] The value for column 0, row 2.
* @param {Number} [column1Row2=0.0] The value for column 1, row 2.
* @param {Number} [column2Row2=0.0] The value for column 2, row 2.
* @param {Number} [column3Row2=0.0] The value for column 3, row 2.
* @param {Number} [column0Row3=0.0] The value for column 0, row 3.
* @param {Number} [column1Row3=0.0] The value for column 1, row 3.
* @param {Number} [column2Row3=0.0] The value for column 2, row 3.
* @param {Number} [column3Row3=0.0] The value for column 3, row 3.
*
* @see Matrix4.fromColumnMajorArray
* @see Matrix4.fromRowMajorArray
* @see Matrix4.fromRotationTranslation
* @see Matrix4.fromTranslationRotationScale
* @see Matrix4.fromTranslationQuaternionRotationScale
* @see Matrix4.fromTranslation
* @see Matrix4.fromScale
* @see Matrix4.fromUniformScale
* @see Matrix4.fromCamera
* @see Matrix4.computePerspectiveFieldOfView
* @see Matrix4.computeOrthographicOffCenter
* @see Matrix4.computePerspectiveOffCenter
* @see Matrix4.computeInfinitePerspectiveOffCenter
* @see Matrix4.computeViewportTransformation
* @see Matrix4.computeView
* @see Matrix2
* @see Matrix3
* @see Packable
*/
function Matrix4(column0Row0, column1Row0, column2Row0, column3Row0,
column0Row1, column1Row1, column2Row1, column3Row1,
column0Row2, column1Row2, column2Row2, column3Row2,
column0Row3, column1Row3, column2Row3, column3Row3) {
this[0] = defaultValue(column0Row0, 0.0);
this[1] = defaultValue(column0Row1, 0.0);
this[2] = defaultValue(column0Row2, 0.0);
this[3] = defaultValue(column0Row3, 0.0);
this[4] = defaultValue(column1Row0, 0.0);
this[5] = defaultValue(column1Row1, 0.0);
this[6] = defaultValue(column1Row2, 0.0);
this[7] = defaultValue(column1Row3, 0.0);
this[8] = defaultValue(column2Row0, 0.0);
this[9] = defaultValue(column2Row1, 0.0);
this[10] = defaultValue(column2Row2, 0.0);
this[11] = defaultValue(column2Row3, 0.0);
this[12] = defaultValue(column3Row0, 0.0);
this[13] = defaultValue(column3Row1, 0.0);
this[14] = defaultValue(column3Row2, 0.0);
this[15] = defaultValue(column3Row3, 0.0);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Matrix4.packedLength = 16;
/**
* Stores the provided instance into the provided array.
*
* @param {Matrix4} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Matrix4.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value[0];
array[startingIndex++] = value[1];
array[startingIndex++] = value[2];
array[startingIndex++] = value[3];
array[startingIndex++] = value[4];
array[startingIndex++] = value[5];
array[startingIndex++] = value[6];
array[startingIndex++] = value[7];
array[startingIndex++] = value[8];
array[startingIndex++] = value[9];
array[startingIndex++] = value[10];
array[startingIndex++] = value[11];
array[startingIndex++] = value[12];
array[startingIndex++] = value[13];
array[startingIndex++] = value[14];
array[startingIndex] = value[15];
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Matrix4} [result] The object into which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
*/
Matrix4.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Matrix4();
}
result[0] = array[startingIndex++];
result[1] = array[startingIndex++];
result[2] = array[startingIndex++];
result[3] = array[startingIndex++];
result[4] = array[startingIndex++];
result[5] = array[startingIndex++];
result[6] = array[startingIndex++];
result[7] = array[startingIndex++];
result[8] = array[startingIndex++];
result[9] = array[startingIndex++];
result[10] = array[startingIndex++];
result[11] = array[startingIndex++];
result[12] = array[startingIndex++];
result[13] = array[startingIndex++];
result[14] = array[startingIndex++];
result[15] = array[startingIndex];
return result;
};
/**
* Duplicates a Matrix4 instance.
*
* @param {Matrix4} matrix The matrix to duplicate.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided. (Returns undefined if matrix is undefined)
*/
Matrix4.clone = function(matrix, result) {
if (!defined(matrix)) {
return undefined;
}
if (!defined(result)) {
return new Matrix4(matrix[0], matrix[4], matrix[8], matrix[12],
matrix[1], matrix[5], matrix[9], matrix[13],
matrix[2], matrix[6], matrix[10], matrix[14],
matrix[3], matrix[7], matrix[11], matrix[15]);
}
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[3];
result[4] = matrix[4];
result[5] = matrix[5];
result[6] = matrix[6];
result[7] = matrix[7];
result[8] = matrix[8];
result[9] = matrix[9];
result[10] = matrix[10];
result[11] = matrix[11];
result[12] = matrix[12];
result[13] = matrix[13];
result[14] = matrix[14];
result[15] = matrix[15];
return result;
};
/**
* Creates a Matrix4 from 16 consecutive elements in an array.
* @function
*
* @param {Number[]} array The array whose 16 consecutive elements correspond to the positions of the matrix. Assumes column-major order.
* @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to first column first row position in the matrix.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
*
* @example
* // Create the Matrix4:
* // [1.0, 2.0, 3.0, 4.0]
* // [1.0, 2.0, 3.0, 4.0]
* // [1.0, 2.0, 3.0, 4.0]
* // [1.0, 2.0, 3.0, 4.0]
*
* var v = [1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 4.0];
* var m = Cesium.Matrix4.fromArray(v);
*
* // Create same Matrix4 with using an offset into an array
* var v2 = [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 4.0];
* var m2 = Cesium.Matrix4.fromArray(v2, 2);
*/
Matrix4.fromArray = Matrix4.unpack;
/**
* Computes a Matrix4 instance from a column-major order array.
*
* @param {Number[]} values The column-major order array.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*/
Matrix4.fromColumnMajorArray = function(values, result) {
if (!defined(values)) {
throw new DeveloperError('values is required');
}
return Matrix4.clone(values, result);
};
/**
* Computes a Matrix4 instance from a row-major order array.
* The resulting matrix will be in column-major order.
*
* @param {Number[]} values The row-major order array.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*/
Matrix4.fromRowMajorArray = function(values, result) {
if (!defined(values)) {
throw new DeveloperError('values is required.');
}
if (!defined(result)) {
return new Matrix4(values[0], values[1], values[2], values[3],
values[4], values[5], values[6], values[7],
values[8], values[9], values[10], values[11],
values[12], values[13], values[14], values[15]);
}
result[0] = values[0];
result[1] = values[4];
result[2] = values[8];
result[3] = values[12];
result[4] = values[1];
result[5] = values[5];
result[6] = values[9];
result[7] = values[13];
result[8] = values[2];
result[9] = values[6];
result[10] = values[10];
result[11] = values[14];
result[12] = values[3];
result[13] = values[7];
result[14] = values[11];
result[15] = values[15];
return result;
};
/**
* Computes a Matrix4 instance from a Matrix3 representing the rotation
* and a Cartesian3 representing the translation.
*
* @param {Matrix3} rotation The upper left portion of the matrix representing the rotation.
* @param {Cartesian3} [translation=Cartesian3.ZERO] The upper right portion of the matrix representing the translation.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*/
Matrix4.fromRotationTranslation = function(rotation, translation, result) {
if (!defined(rotation)) {
throw new DeveloperError('rotation is required.');
}
translation = defaultValue(translation, Cartesian3.ZERO);
if (!defined(result)) {
return new Matrix4(rotation[0], rotation[3], rotation[6], translation.x,
rotation[1], rotation[4], rotation[7], translation.y,
rotation[2], rotation[5], rotation[8], translation.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = rotation[0];
result[1] = rotation[1];
result[2] = rotation[2];
result[3] = 0.0;
result[4] = rotation[3];
result[5] = rotation[4];
result[6] = rotation[5];
result[7] = 0.0;
result[8] = rotation[6];
result[9] = rotation[7];
result[10] = rotation[8];
result[11] = 0.0;
result[12] = translation.x;
result[13] = translation.y;
result[14] = translation.z;
result[15] = 1.0;
return result;
};
/**
* Computes a Matrix4 instance from a translation, rotation, and scale (TRS)
* representation with the rotation represented as a quaternion.
*
* @param {Cartesian3} translation The translation transformation.
* @param {Quaternion} rotation The rotation transformation.
* @param {Cartesian3} scale The non-uniform scale transformation.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*
* @example
* var result = Cesium.Matrix4.fromTranslationQuaternionRotationScale(
* new Cesium.Cartesian3(1.0, 2.0, 3.0), // translation
* Cesium.Quaternion.IDENTITY, // rotation
* new Cesium.Cartesian3(7.0, 8.0, 9.0), // scale
* result);
*/
Matrix4.fromTranslationQuaternionRotationScale = function(translation, rotation, scale, result) {
if (!defined(translation)) {
throw new DeveloperError('translation is required.');
}
if (!defined(rotation)) {
throw new DeveloperError('rotation is required.');
}
if (!defined(scale)) {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
result = new Matrix4();
}
var scaleX = scale.x;
var scaleY = scale.y;
var scaleZ = scale.z;
var x2 = rotation.x * rotation.x;
var xy = rotation.x * rotation.y;
var xz = rotation.x * rotation.z;
var xw = rotation.x * rotation.w;
var y2 = rotation.y * rotation.y;
var yz = rotation.y * rotation.z;
var yw = rotation.y * rotation.w;
var z2 = rotation.z * rotation.z;
var zw = rotation.z * rotation.w;
var w2 = rotation.w * rotation.w;
var m00 = x2 - y2 - z2 + w2;
var m01 = 2.0 * (xy - zw);
var m02 = 2.0 * (xz + yw);
var m10 = 2.0 * (xy + zw);
var m11 = -x2 + y2 - z2 + w2;
var m12 = 2.0 * (yz - xw);
var m20 = 2.0 * (xz - yw);
var m21 = 2.0 * (yz + xw);
var m22 = -x2 - y2 + z2 + w2;
result[0] = m00 * scaleX;
result[1] = m10 * scaleX;
result[2] = m20 * scaleX;
result[3] = 0.0;
result[4] = m01 * scaleY;
result[5] = m11 * scaleY;
result[6] = m21 * scaleY;
result[7] = 0.0;
result[8] = m02 * scaleZ;
result[9] = m12 * scaleZ;
result[10] = m22 * scaleZ;
result[11] = 0.0;
result[12] = translation.x;
result[13] = translation.y;
result[14] = translation.z;
result[15] = 1.0;
return result;
};
/**
* Creates a Matrix4 instance from a {@link TranslationRotationScale} instance.
*
* @param {TranslationRotationScale} translationRotationScale The instance.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*/
Matrix4.fromTranslationRotationScale = function(translationRotationScale, result) {
if (!defined(translationRotationScale)) {
throw new DeveloperError('translationRotationScale is required.');
}
return Matrix4.fromTranslationQuaternionRotationScale(translationRotationScale.translation, translationRotationScale.rotation, translationRotationScale.scale, result);
};
/**
* Creates a Matrix4 instance from a Cartesian3 representing the translation.
*
* @param {Cartesian3} translation The upper right portion of the matrix representing the translation.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*
* @see Matrix4.multiplyByTranslation
*/
Matrix4.fromTranslation = function(translation, result) {
if (!defined(translation)) {
throw new DeveloperError('translation is required.');
}
return Matrix4.fromRotationTranslation(Matrix3.IDENTITY, translation, result);
};
/**
* Computes a Matrix4 instance representing a non-uniform scale.
*
* @param {Cartesian3} scale The x, y, and z scale factors.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*
* @example
* // Creates
* // [7.0, 0.0, 0.0, 0.0]
* // [0.0, 8.0, 0.0, 0.0]
* // [0.0, 0.0, 9.0, 0.0]
* // [0.0, 0.0, 0.0, 1.0]
* var m = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(7.0, 8.0, 9.0));
*/
Matrix4.fromScale = function(scale, result) {
if (!defined(scale)) {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
return new Matrix4(
scale.x, 0.0, 0.0, 0.0,
0.0, scale.y, 0.0, 0.0,
0.0, 0.0, scale.z, 0.0,
0.0, 0.0, 0.0, 1.0);
}
result[0] = scale.x;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = scale.y;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = scale.z;
result[11] = 0.0;
result[12] = 0.0;
result[13] = 0.0;
result[14] = 0.0;
result[15] = 1.0;
return result;
};
/**
* Computes a Matrix4 instance representing a uniform scale.
*
* @param {Number} scale The uniform scale factor.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*
* @example
* // Creates
* // [2.0, 0.0, 0.0, 0.0]
* // [0.0, 2.0, 0.0, 0.0]
* // [0.0, 0.0, 2.0, 0.0]
* // [0.0, 0.0, 0.0, 1.0]
* var m = Cesium.Matrix4.fromUniformScale(2.0);
*/
Matrix4.fromUniformScale = function(scale, result) {
if (typeof scale !== 'number') {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
return new Matrix4(scale, 0.0, 0.0, 0.0,
0.0, scale, 0.0, 0.0,
0.0, 0.0, scale, 0.0,
0.0, 0.0, 0.0, 1.0);
}
result[0] = scale;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = scale;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = scale;
result[11] = 0.0;
result[12] = 0.0;
result[13] = 0.0;
result[14] = 0.0;
result[15] = 1.0;
return result;
};
var fromCameraF = new Cartesian3();
var fromCameraR = new Cartesian3();
var fromCameraU = new Cartesian3();
/**
* Computes a Matrix4 instance from a Camera.
*
* @param {Camera} camera The camera to use.
* @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
*/
Matrix4.fromCamera = function(camera, result) {
if (!defined(camera)) {
throw new DeveloperError('camera is required.');
}
var position = camera.position;
var direction = camera.direction;
var up = camera.up;
if (!defined(position)) {
throw new DeveloperError('camera.position is required.');
}
if (!defined(direction)) {
throw new DeveloperError('camera.direction is required.');
}
if (!defined(up)) {
throw new DeveloperError('camera.up is required.');
}
Cartesian3.normalize(direction, fromCameraF);
Cartesian3.normalize(Cartesian3.cross(fromCameraF, up, fromCameraR), fromCameraR);
Cartesian3.normalize(Cartesian3.cross(fromCameraR, fromCameraF, fromCameraU), fromCameraU);
var sX = fromCameraR.x;
var sY = fromCameraR.y;
var sZ = fromCameraR.z;
var fX = fromCameraF.x;
var fY = fromCameraF.y;
var fZ = fromCameraF.z;
var uX = fromCameraU.x;
var uY = fromCameraU.y;
var uZ = fromCameraU.z;
var positionX = position.x;
var positionY = position.y;
var positionZ = position.z;
var t0 = sX * -positionX + sY * -positionY+ sZ * -positionZ;
var t1 = uX * -positionX + uY * -positionY+ uZ * -positionZ;
var t2 = fX * positionX + fY * positionY + fZ * positionZ;
// The code below this comment is an optimized
// version of the commented lines.
// Rather that create two matrices and then multiply,
// we just bake in the multiplcation as part of creation.
// var rotation = new Matrix4(
// sX, sY, sZ, 0.0,
// uX, uY, uZ, 0.0,
// -fX, -fY, -fZ, 0.0,
// 0.0, 0.0, 0.0, 1.0);
// var translation = new Matrix4(
// 1.0, 0.0, 0.0, -position.x,
// 0.0, 1.0, 0.0, -position.y,
// 0.0, 0.0, 1.0, -position.z,
// 0.0, 0.0, 0.0, 1.0);
// return rotation.multiply(translation);
if (!defined(result)) {
return new Matrix4(
sX, sY, sZ, t0,
uX, uY, uZ, t1,
-fX, -fY, -fZ, t2,
0.0, 0.0, 0.0, 1.0);
}
result[0] = sX;
result[1] = uX;
result[2] = -fX;
result[3] = 0.0;
result[4] = sY;
result[5] = uY;
result[6] = -fY;
result[7] = 0.0;
result[8] = sZ;
result[9] = uZ;
result[10] = -fZ;
result[11] = 0.0;
result[12] = t0;
result[13] = t1;
result[14] = t2;
result[15] = 1.0;
return result;
};
/**
* Computes a Matrix4 instance representing a perspective transformation matrix.
*
* @param {Number} fovY The field of view along the Y axis in radians.
* @param {Number} aspectRatio The aspect ratio.
* @param {Number} near The distance to the near plane in meters.
* @param {Number} far The distance to the far plane in meters.
* @param {Matrix4} result The object in which the result will be stored.
* @returns {Matrix4} The modified result parameter.
*
* @exception {DeveloperError} fovY must be in (0, PI].
* @exception {DeveloperError} aspectRatio must be greater than zero.
* @exception {DeveloperError} near must be greater than zero.
* @exception {DeveloperError} far must be greater than zero.
*/
Matrix4.computePerspectiveFieldOfView = function(fovY, aspectRatio, near, far, result) {
if (fovY <= 0.0 || fovY > Math.PI) {
throw new DeveloperError('fovY must be in (0, PI].');
}
if (aspectRatio <= 0.0) {
throw new DeveloperError('aspectRatio must be greater than zero.');
}
if (near <= 0.0) {
throw new DeveloperError('near must be greater than zero.');
}
if (far <= 0.0) {
throw new DeveloperError('far must be greater than zero.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var bottom = Math.tan(fovY * 0.5);
var column1Row1 = 1.0 / bottom;
var column0Row0 = column1Row1 / aspectRatio;
var column2Row2 = (far + near) / (near - far);
var column3Row2 = (2.0 * far * near) / (near - far);
result[0] = column0Row0;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = column1Row1;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = column2Row2;
result[11] = -1.0;
result[12] = 0.0;
result[13] = 0.0;
result[14] = column3Row2;
result[15] = 0.0;
return result;
};
/**
* Computes a Matrix4 instance representing an orthographic transformation matrix.
*
* @param {Number} left The number of meters to the left of the camera that will be in view.
* @param {Number} right The number of meters to the right of the camera that will be in view.
* @param {Number} bottom The number of meters below of the camera that will be in view.
* @param {Number} top The number of meters above of the camera that will be in view.
* @param {Number} near The distance to the near plane in meters.
* @param {Number} far The distance to the far plane in meters.
* @param {Matrix4} result The object in which the result will be stored.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.computeOrthographicOffCenter = function(left, right, bottom, top, near, far, result) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
if (!defined(bottom)) {
throw new DeveloperError('bottom is required.');
}
if (!defined(top)) {
throw new DeveloperError('top is required.');
}
if (!defined(near)) {
throw new DeveloperError('near is required.');
}
if (!defined(far)) {
throw new DeveloperError('far is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var a = 1.0 / (right - left);
var b = 1.0 / (top - bottom);
var c = 1.0 / (far - near);
var tx = -(right + left) * a;
var ty = -(top + bottom) * b;
var tz = -(far + near) * c;
a *= 2.0;
b *= 2.0;
c *= -2.0;
result[0] = a;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = b;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = c;
result[11] = 0.0;
result[12] = tx;
result[13] = ty;
result[14] = tz;
result[15] = 1.0;
return result;
};
/**
* Computes a Matrix4 instance representing an off center perspective transformation.
*
* @param {Number} left The number of meters to the left of the camera that will be in view.
* @param {Number} right The number of meters to the right of the camera that will be in view.
* @param {Number} bottom The number of meters below of the camera that will be in view.
* @param {Number} top The number of meters above of the camera that will be in view.
* @param {Number} near The distance to the near plane in meters.
* @param {Number} far The distance to the far plane in meters.
* @param {Matrix4} result The object in which the result will be stored.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.computePerspectiveOffCenter = function(left, right, bottom, top, near, far, result) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
if (!defined(bottom)) {
throw new DeveloperError('bottom is required.');
}
if (!defined(top)) {
throw new DeveloperError('top is required.');
}
if (!defined(near)) {
throw new DeveloperError('near is required.');
}
if (!defined(far)) {
throw new DeveloperError('far is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var column0Row0 = 2.0 * near / (right - left);
var column1Row1 = 2.0 * near / (top - bottom);
var column2Row0 = (right + left) / (right - left);
var column2Row1 = (top + bottom) / (top - bottom);
var column2Row2 = -(far + near) / (far - near);
var column2Row3 = -1.0;
var column3Row2 = -2.0 * far * near / (far - near);
result[0] = column0Row0;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = column1Row1;
result[6] = 0.0;
result[7] = 0.0;
result[8] = column2Row0;
result[9] = column2Row1;
result[10] = column2Row2;
result[11] = column2Row3;
result[12] = 0.0;
result[13] = 0.0;
result[14] = column3Row2;
result[15] = 0.0;
return result;
};
/**
* Computes a Matrix4 instance representing an infinite off center perspective transformation.
*
* @param {Number} left The number of meters to the left of the camera that will be in view.
* @param {Number} right The number of meters to the right of the camera that will be in view.
* @param {Number} bottom The number of meters below of the camera that will be in view.
* @param {Number} top The number of meters above of the camera that will be in view.
* @param {Number} near The distance to the near plane in meters.
* @param {Matrix4} result The object in which the result will be stored.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.computeInfinitePerspectiveOffCenter = function(left, right, bottom, top, near, result) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
if (!defined(bottom)) {
throw new DeveloperError('bottom is required.');
}
if (!defined(top)) {
throw new DeveloperError('top is required.');
}
if (!defined(near)) {
throw new DeveloperError('near is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var column0Row0 = 2.0 * near / (right - left);
var column1Row1 = 2.0 * near / (top - bottom);
var column2Row0 = (right + left) / (right - left);
var column2Row1 = (top + bottom) / (top - bottom);
var column2Row2 = -1.0;
var column2Row3 = -1.0;
var column3Row2 = -2.0 * near;
result[0] = column0Row0;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = column1Row1;
result[6] = 0.0;
result[7] = 0.0;
result[8] = column2Row0;
result[9] = column2Row1;
result[10] = column2Row2;
result[11] = column2Row3;
result[12] = 0.0;
result[13] = 0.0;
result[14] = column3Row2;
result[15] = 0.0;
return result;
};
/**
* Computes a Matrix4 instance that transforms from normalized device coordinates to window coordinates.
*
* @param {Object}[viewport = { x : 0.0, y : 0.0, width : 0.0, height : 0.0 }] The viewport's corners as shown in Example 1.
* @param {Number}[nearDepthRange=0.0] The near plane distance in window coordinates.
* @param {Number}[farDepthRange=1.0] The far plane distance in window coordinates.
* @param {Matrix4} result The object in which the result will be stored.
* @returns {Matrix4} The modified result parameter.
*
* @example
* // Create viewport transformation using an explicit viewport and depth range.
* var m = Cesium.Matrix4.computeViewportTransformation({
* x : 0.0,
* y : 0.0,
* width : 1024.0,
* height : 768.0
* }, 0.0, 1.0, new Cesium.Matrix4());
*/
Matrix4.computeViewportTransformation = function(viewport, nearDepthRange, farDepthRange, result) {
if (!defined(result)) {
throw new DeveloperError('result is required');
}
viewport = defaultValue(viewport, defaultValue.EMPTY_OBJECT);
var x = defaultValue(viewport.x, 0.0);
var y = defaultValue(viewport.y, 0.0);
var width = defaultValue(viewport.width, 0.0);
var height = defaultValue(viewport.height, 0.0);
nearDepthRange = defaultValue(nearDepthRange, 0.0);
farDepthRange = defaultValue(farDepthRange, 1.0);
var halfWidth = width * 0.5;
var halfHeight = height * 0.5;
var halfDepth = (farDepthRange - nearDepthRange) * 0.5;
var column0Row0 = halfWidth;
var column1Row1 = halfHeight;
var column2Row2 = halfDepth;
var column3Row0 = x + halfWidth;
var column3Row1 = y + halfHeight;
var column3Row2 = nearDepthRange + halfDepth;
var column3Row3 = 1.0;
result[0] = column0Row0;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = column1Row1;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = column2Row2;
result[11] = 0.0;
result[12] = column3Row0;
result[13] = column3Row1;
result[14] = column3Row2;
result[15] = column3Row3;
return result;
};
/**
* Computes a Matrix4 instance that transforms from world space to view space.
*
* @param {Cartesian3} position The position of the camera.
* @param {Cartesian3} direction The forward direction.
* @param {Cartesian3} up The up direction.
* @param {Cartesian3} right The right direction.
* @param {Matrix4} result The object in which the result will be stored.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.computeView = function(position, direction, up, right, result) {
if (!defined(position)) {
throw new DeveloperError('position is required');
}
if (!defined(direction)) {
throw new DeveloperError('direction is required');
}
if (!defined(up)) {
throw new DeveloperError('up is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = right.x;
result[1] = up.x;
result[2] = -direction.x;
result[3] = 0.0;
result[4] = right.y;
result[5] = up.y;
result[6] = -direction.y;
result[7] = 0.0;
result[8] = right.z;
result[9] = up.z;
result[10] = -direction.z;
result[11] = 0.0;
result[12] = -Cartesian3.dot(right, position);
result[13] = -Cartesian3.dot(up, position);
result[14] = Cartesian3.dot(direction, position);
result[15] = 1.0;
return result;
};
/**
* Computes an Array from the provided Matrix4 instance.
* The array will be in column-major order.
*
* @param {Matrix4} matrix The matrix to use..
* @param {Number[]} [result] The Array onto which to store the result.
* @returns {Number[]} The modified Array parameter or a new Array instance if one was not provided.
*
* @example
* //create an array from an instance of Matrix4
* // m = [10.0, 14.0, 18.0, 22.0]
* // [11.0, 15.0, 19.0, 23.0]
* // [12.0, 16.0, 20.0, 24.0]
* // [13.0, 17.0, 21.0, 25.0]
* var a = Cesium.Matrix4.toArray(m);
*
* // m remains the same
* //creates a = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0]
*/
Matrix4.toArray = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
return [matrix[0], matrix[1], matrix[2], matrix[3],
matrix[4], matrix[5], matrix[6], matrix[7],
matrix[8], matrix[9], matrix[10], matrix[11],
matrix[12], matrix[13], matrix[14], matrix[15]];
}
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[3];
result[4] = matrix[4];
result[5] = matrix[5];
result[6] = matrix[6];
result[7] = matrix[7];
result[8] = matrix[8];
result[9] = matrix[9];
result[10] = matrix[10];
result[11] = matrix[11];
result[12] = matrix[12];
result[13] = matrix[13];
result[14] = matrix[14];
result[15] = matrix[15];
return result;
};
/**
* Computes the array index of the element at the provided row and column.
*
* @param {Number} row The zero-based index of the row.
* @param {Number} column The zero-based index of the column.
* @returns {Number} The index of the element at the provided row and column.
*
* @exception {DeveloperError} row must be 0, 1, 2, or 3.
* @exception {DeveloperError} column must be 0, 1, 2, or 3.
*
* @example
* var myMatrix = new Cesium.Matrix4();
* var column1Row0Index = Cesium.Matrix4.getElementIndex(1, 0);
* var column1Row0 = myMatrix[column1Row0Index];
* myMatrix[column1Row0Index] = 10.0;
*/
Matrix4.getElementIndex = function(column, row) {
if (typeof row !== 'number' || row < 0 || row > 3) {
throw new DeveloperError('row must be 0, 1, 2, or 3.');
}
if (typeof column !== 'number' || column < 0 || column > 3) {
throw new DeveloperError('column must be 0, 1, 2, or 3.');
}
return column * 4 + row;
};
/**
* Retrieves a copy of the matrix column at the provided index as a Cartesian4 instance.
*
* @param {Matrix4} matrix The matrix to use.
* @param {Number} index The zero-based index of the column to retrieve.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, 2, or 3.
*
* @example
* //returns a Cartesian4 instance with values from the specified column
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* //Example 1: Creates an instance of Cartesian
* var a = Cesium.Matrix4.getColumn(m, 2, new Cesium.Cartesian4());
*
* @example
* //Example 2: Sets values for Cartesian instance
* var a = new Cesium.Cartesian4();
* Cesium.Matrix4.getColumn(m, 2, a);
*
* // a.x = 12.0; a.y = 16.0; a.z = 20.0; a.w = 24.0;
*/
Matrix4.getColumn = function(matrix, index, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (typeof index !== 'number' || index < 0 || index > 3) {
throw new DeveloperError('index must be 0, 1, 2, or 3.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var startIndex = index * 4;
var x = matrix[startIndex];
var y = matrix[startIndex + 1];
var z = matrix[startIndex + 2];
var w = matrix[startIndex + 3];
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
/**
* Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian4 instance.
*
* @param {Matrix4} matrix The matrix to use.
* @param {Number} index The zero-based index of the column to set.
* @param {Cartesian4} cartesian The Cartesian whose values will be assigned to the specified column.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, 2, or 3.
*
* @example
* //creates a new Matrix4 instance with new column values from the Cartesian4 instance
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* var a = Cesium.Matrix4.setColumn(m, 2, new Cesium.Cartesian4(99.0, 98.0, 97.0, 96.0), new Cesium.Matrix4());
*
* // m remains the same
* // a = [10.0, 11.0, 99.0, 13.0]
* // [14.0, 15.0, 98.0, 17.0]
* // [18.0, 19.0, 97.0, 21.0]
* // [22.0, 23.0, 96.0, 25.0]
*/
Matrix4.setColumn = function(matrix, index, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof index !== 'number' || index < 0 || index > 3) {
throw new DeveloperError('index must be 0, 1, 2, or 3.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result = Matrix4.clone(matrix, result);
var startIndex = index * 4;
result[startIndex] = cartesian.x;
result[startIndex + 1] = cartesian.y;
result[startIndex + 2] = cartesian.z;
result[startIndex + 3] = cartesian.w;
return result;
};
/**
* Computes a new matrix that replaces the translation in the rightmost column of the provided
* matrix with the provided translation. This assumes the matrix is an affine transformation
*
* @param {Matrix4} matrix The matrix to use.
* @param {Cartesian3} translation The translation that replaces the translation of the provided matrix.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.setTranslation = function(matrix, translation, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(translation)) {
throw new DeveloperError('translation is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[3];
result[4] = matrix[4];
result[5] = matrix[5];
result[6] = matrix[6];
result[7] = matrix[7];
result[8] = matrix[8];
result[9] = matrix[9];
result[10] = matrix[10];
result[11] = matrix[11];
result[12] = translation.x;
result[13] = translation.y;
result[14] = translation.z;
result[15] = matrix[15];
return result;
};
/**
* Retrieves a copy of the matrix row at the provided index as a Cartesian4 instance.
*
* @param {Matrix4} matrix The matrix to use.
* @param {Number} index The zero-based index of the row to retrieve.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, 2, or 3.
*
* @example
* //returns a Cartesian4 instance with values from the specified column
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* //Example 1: Returns an instance of Cartesian
* var a = Cesium.Matrix4.getRow(m, 2, new Cesium.Cartesian4());
*
* @example
* //Example 2: Sets values for a Cartesian instance
* var a = new Cesium.Cartesian4();
* Cesium.Matrix4.getRow(m, 2, a);
*
* // a.x = 18.0; a.y = 19.0; a.z = 20.0; a.w = 21.0;
*/
Matrix4.getRow = function(matrix, index, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (typeof index !== 'number' || index < 0 || index > 3) {
throw new DeveloperError('index must be 0, 1, 2, or 3.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var x = matrix[index];
var y = matrix[index + 4];
var z = matrix[index + 8];
var w = matrix[index + 12];
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
/**
* Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian4 instance.
*
* @param {Matrix4} matrix The matrix to use.
* @param {Number} index The zero-based index of the row to set.
* @param {Cartesian4} cartesian The Cartesian whose values will be assigned to the specified row.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @exception {DeveloperError} index must be 0, 1, 2, or 3.
*
* @example
* //create a new Matrix4 instance with new row values from the Cartesian4 instance
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* var a = Cesium.Matrix4.setRow(m, 2, new Cesium.Cartesian4(99.0, 98.0, 97.0, 96.0), new Cesium.Matrix4());
*
* // m remains the same
* // a = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [99.0, 98.0, 97.0, 96.0]
* // [22.0, 23.0, 24.0, 25.0]
*/
Matrix4.setRow = function(matrix, index, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof index !== 'number' || index < 0 || index > 3) {
throw new DeveloperError('index must be 0, 1, 2, or 3.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result = Matrix4.clone(matrix, result);
result[index] = cartesian.x;
result[index + 4] = cartesian.y;
result[index + 8] = cartesian.z;
result[index + 12] = cartesian.w;
return result;
};
var scratchColumn = new Cartesian3();
/**
* Extracts the non-uniform scale assuming the matrix is an affine transformation.
*
* @param {Matrix4} matrix The matrix.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter
*/
Matrix4.getScale = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = Cartesian3.magnitude(Cartesian3.fromElements(matrix[0], matrix[1], matrix[2], scratchColumn));
result.y = Cartesian3.magnitude(Cartesian3.fromElements(matrix[4], matrix[5], matrix[6], scratchColumn));
result.z = Cartesian3.magnitude(Cartesian3.fromElements(matrix[8], matrix[9], matrix[10], scratchColumn));
return result;
};
var scratchScale = new Cartesian3();
/**
* Computes the maximum scale assuming the matrix is an affine transformation.
* The maximum scale is the maximum length of the column vectors in the upper-left
* 3x3 matrix.
*
* @param {Matrix4} matrix The matrix.
* @returns {Number} The maximum scale.
*/
Matrix4.getMaximumScale = function(matrix) {
Matrix4.getScale(matrix, scratchScale);
return Cartesian3.maximumComponent(scratchScale);
};
/**
* Computes the product of two matrices.
*
* @param {Matrix4} left The first matrix.
* @param {Matrix4} right The second matrix.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.multiply = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var left0 = left[0];
var left1 = left[1];
var left2 = left[2];
var left3 = left[3];
var left4 = left[4];
var left5 = left[5];
var left6 = left[6];
var left7 = left[7];
var left8 = left[8];
var left9 = left[9];
var left10 = left[10];
var left11 = left[11];
var left12 = left[12];
var left13 = left[13];
var left14 = left[14];
var left15 = left[15];
var right0 = right[0];
var right1 = right[1];
var right2 = right[2];
var right3 = right[3];
var right4 = right[4];
var right5 = right[5];
var right6 = right[6];
var right7 = right[7];
var right8 = right[8];
var right9 = right[9];
var right10 = right[10];
var right11 = right[11];
var right12 = right[12];
var right13 = right[13];
var right14 = right[14];
var right15 = right[15];
var column0Row0 = left0 * right0 + left4 * right1 + left8 * right2 + left12 * right3;
var column0Row1 = left1 * right0 + left5 * right1 + left9 * right2 + left13 * right3;
var column0Row2 = left2 * right0 + left6 * right1 + left10 * right2 + left14 * right3;
var column0Row3 = left3 * right0 + left7 * right1 + left11 * right2 + left15 * right3;
var column1Row0 = left0 * right4 + left4 * right5 + left8 * right6 + left12 * right7;
var column1Row1 = left1 * right4 + left5 * right5 + left9 * right6 + left13 * right7;
var column1Row2 = left2 * right4 + left6 * right5 + left10 * right6 + left14 * right7;
var column1Row3 = left3 * right4 + left7 * right5 + left11 * right6 + left15 * right7;
var column2Row0 = left0 * right8 + left4 * right9 + left8 * right10 + left12 * right11;
var column2Row1 = left1 * right8 + left5 * right9 + left9 * right10 + left13 * right11;
var column2Row2 = left2 * right8 + left6 * right9 + left10 * right10 + left14 * right11;
var column2Row3 = left3 * right8 + left7 * right9 + left11 * right10 + left15 * right11;
var column3Row0 = left0 * right12 + left4 * right13 + left8 * right14 + left12 * right15;
var column3Row1 = left1 * right12 + left5 * right13 + left9 * right14 + left13 * right15;
var column3Row2 = left2 * right12 + left6 * right13 + left10 * right14 + left14 * right15;
var column3Row3 = left3 * right12 + left7 * right13 + left11 * right14 + left15 * right15;
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column0Row2;
result[3] = column0Row3;
result[4] = column1Row0;
result[5] = column1Row1;
result[6] = column1Row2;
result[7] = column1Row3;
result[8] = column2Row0;
result[9] = column2Row1;
result[10] = column2Row2;
result[11] = column2Row3;
result[12] = column3Row0;
result[13] = column3Row1;
result[14] = column3Row2;
result[15] = column3Row3;
return result;
};
/**
* Computes the sum of two matrices.
*
* @param {Matrix4} left The first matrix.
* @param {Matrix4} right The second matrix.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = left[0] + right[0];
result[1] = left[1] + right[1];
result[2] = left[2] + right[2];
result[3] = left[3] + right[3];
result[4] = left[4] + right[4];
result[5] = left[5] + right[5];
result[6] = left[6] + right[6];
result[7] = left[7] + right[7];
result[8] = left[8] + right[8];
result[9] = left[9] + right[9];
result[10] = left[10] + right[10];
result[11] = left[11] + right[11];
result[12] = left[12] + right[12];
result[13] = left[13] + right[13];
result[14] = left[14] + right[14];
result[15] = left[15] + right[15];
return result;
};
/**
* Computes the difference of two matrices.
*
* @param {Matrix4} left The first matrix.
* @param {Matrix4} right The second matrix.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = left[0] - right[0];
result[1] = left[1] - right[1];
result[2] = left[2] - right[2];
result[3] = left[3] - right[3];
result[4] = left[4] - right[4];
result[5] = left[5] - right[5];
result[6] = left[6] - right[6];
result[7] = left[7] - right[7];
result[8] = left[8] - right[8];
result[9] = left[9] - right[9];
result[10] = left[10] - right[10];
result[11] = left[11] - right[11];
result[12] = left[12] - right[12];
result[13] = left[13] - right[13];
result[14] = left[14] - right[14];
result[15] = left[15] - right[15];
return result;
};
/**
* Computes the product of two matrices assuming the matrices are
* affine transformation matrices, where the upper left 3x3 elements
* are a rotation matrix, and the upper three elements in the fourth
* column are the translation. The bottom row is assumed to be [0, 0, 0, 1].
* The matrix is not verified to be in the proper form.
* This method is faster than computing the product for general 4x4
* matrices using {@link Matrix4.multiply}.
*
* @param {Matrix4} left The first matrix.
* @param {Matrix4} right The second matrix.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @example
* var m1 = new Cesium.Matrix4(1.0, 6.0, 7.0, 0.0, 2.0, 5.0, 8.0, 0.0, 3.0, 4.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0);
* var m2 = Cesium.Transforms.eastNorthUpToFixedFrame(new Cesium.Cartesian3(1.0, 1.0, 1.0));
* var m3 = Cesium.Matrix4.multiplyTransformation(m1, m2, new Cesium.Matrix4());
*/
Matrix4.multiplyTransformation = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var left0 = left[0];
var left1 = left[1];
var left2 = left[2];
var left4 = left[4];
var left5 = left[5];
var left6 = left[6];
var left8 = left[8];
var left9 = left[9];
var left10 = left[10];
var left12 = left[12];
var left13 = left[13];
var left14 = left[14];
var right0 = right[0];
var right1 = right[1];
var right2 = right[2];
var right4 = right[4];
var right5 = right[5];
var right6 = right[6];
var right8 = right[8];
var right9 = right[9];
var right10 = right[10];
var right12 = right[12];
var right13 = right[13];
var right14 = right[14];
var column0Row0 = left0 * right0 + left4 * right1 + left8 * right2;
var column0Row1 = left1 * right0 + left5 * right1 + left9 * right2;
var column0Row2 = left2 * right0 + left6 * right1 + left10 * right2;
var column1Row0 = left0 * right4 + left4 * right5 + left8 * right6;
var column1Row1 = left1 * right4 + left5 * right5 + left9 * right6;
var column1Row2 = left2 * right4 + left6 * right5 + left10 * right6;
var column2Row0 = left0 * right8 + left4 * right9 + left8 * right10;
var column2Row1 = left1 * right8 + left5 * right9 + left9 * right10;
var column2Row2 = left2 * right8 + left6 * right9 + left10 * right10;
var column3Row0 = left0 * right12 + left4 * right13 + left8 * right14 + left12;
var column3Row1 = left1 * right12 + left5 * right13 + left9 * right14 + left13;
var column3Row2 = left2 * right12 + left6 * right13 + left10 * right14 + left14;
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column0Row2;
result[3] = 0.0;
result[4] = column1Row0;
result[5] = column1Row1;
result[6] = column1Row2;
result[7] = 0.0;
result[8] = column2Row0;
result[9] = column2Row1;
result[10] = column2Row2;
result[11] = 0.0;
result[12] = column3Row0;
result[13] = column3Row1;
result[14] = column3Row2;
result[15] = 1.0;
return result;
};
/**
* Multiplies a transformation matrix (with a bottom row of [0.0, 0.0, 0.0, 1.0]
)
* by a 3x3 rotation matrix. This is an optimization
* for Matrix4.multiply(m, Matrix4.fromRotationTranslation(rotation), m);
with less allocations and arithmetic operations.
*
* @param {Matrix4} matrix The matrix on the left-hand side.
* @param {Matrix3} rotation The 3x3 rotation matrix on the right-hand side.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @example
* // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromRotationTranslation(rotation), m);
* Cesium.Matrix4.multiplyByMatrix3(m, rotation, m);
*/
Matrix4.multiplyByMatrix3 = function(matrix, rotation, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(rotation)) {
throw new DeveloperError('rotation is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var left0 = matrix[0];
var left1 = matrix[1];
var left2 = matrix[2];
var left4 = matrix[4];
var left5 = matrix[5];
var left6 = matrix[6];
var left8 = matrix[8];
var left9 = matrix[9];
var left10 = matrix[10];
var right0 = rotation[0];
var right1 = rotation[1];
var right2 = rotation[2];
var right4 = rotation[3];
var right5 = rotation[4];
var right6 = rotation[5];
var right8 = rotation[6];
var right9 = rotation[7];
var right10 = rotation[8];
var column0Row0 = left0 * right0 + left4 * right1 + left8 * right2;
var column0Row1 = left1 * right0 + left5 * right1 + left9 * right2;
var column0Row2 = left2 * right0 + left6 * right1 + left10 * right2;
var column1Row0 = left0 * right4 + left4 * right5 + left8 * right6;
var column1Row1 = left1 * right4 + left5 * right5 + left9 * right6;
var column1Row2 = left2 * right4 + left6 * right5 + left10 * right6;
var column2Row0 = left0 * right8 + left4 * right9 + left8 * right10;
var column2Row1 = left1 * right8 + left5 * right9 + left9 * right10;
var column2Row2 = left2 * right8 + left6 * right9 + left10 * right10;
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column0Row2;
result[3] = 0.0;
result[4] = column1Row0;
result[5] = column1Row1;
result[6] = column1Row2;
result[7] = 0.0;
result[8] = column2Row0;
result[9] = column2Row1;
result[10] = column2Row2;
result[11] = 0.0;
result[12] = matrix[12];
result[13] = matrix[13];
result[14] = matrix[14];
result[15] = matrix[15];
return result;
};
/**
* Multiplies a transformation matrix (with a bottom row of [0.0, 0.0, 0.0, 1.0]
)
* by an implicit translation matrix defined by a {@link Cartesian3}. This is an optimization
* for Matrix4.multiply(m, Matrix4.fromTranslation(position), m);
with less allocations and arithmetic operations.
*
* @param {Matrix4} matrix The matrix on the left-hand side.
* @param {Cartesian3} translation The translation on the right-hand side.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @example
* // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromTranslation(position), m);
* Cesium.Matrix4.multiplyByTranslation(m, position, m);
*/
Matrix4.multiplyByTranslation = function(matrix, translation, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(translation)) {
throw new DeveloperError('translation is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var x = translation.x;
var y = translation.y;
var z = translation.z;
var tx = (x * matrix[0]) + (y * matrix[4]) + (z * matrix[8]) + matrix[12];
var ty = (x * matrix[1]) + (y * matrix[5]) + (z * matrix[9]) + matrix[13];
var tz = (x * matrix[2]) + (y * matrix[6]) + (z * matrix[10]) + matrix[14];
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[3];
result[4] = matrix[4];
result[5] = matrix[5];
result[6] = matrix[6];
result[7] = matrix[7];
result[8] = matrix[8];
result[9] = matrix[9];
result[10] = matrix[10];
result[11] = matrix[11];
result[12] = tx;
result[13] = ty;
result[14] = tz;
result[15] = matrix[15];
return result;
};
var uniformScaleScratch = new Cartesian3();
/**
* Multiplies an affine transformation matrix (with a bottom row of [0.0, 0.0, 0.0, 1.0]
)
* by an implicit uniform scale matrix. This is an optimization
* for Matrix4.multiply(m, Matrix4.fromUniformScale(scale), m);
, where
* m
must be an affine matrix.
* This function performs fewer allocations and arithmetic operations.
*
* @param {Matrix4} matrix The affine matrix on the left-hand side.
* @param {Number} scale The uniform scale on the right-hand side.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
*
* @example
* // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromUniformScale(scale), m);
* Cesium.Matrix4.multiplyByUniformScale(m, scale, m);
*
* @see Matrix4.fromUniformScale
* @see Matrix4.multiplyByScale
*/
Matrix4.multiplyByUniformScale = function(matrix, scale, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (typeof scale !== 'number') {
throw new DeveloperError('scale is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
uniformScaleScratch.x = scale;
uniformScaleScratch.y = scale;
uniformScaleScratch.z = scale;
return Matrix4.multiplyByScale(matrix, uniformScaleScratch, result);
};
/**
* Multiplies an affine transformation matrix (with a bottom row of [0.0, 0.0, 0.0, 1.0]
)
* by an implicit non-uniform scale matrix. This is an optimization
* for Matrix4.multiply(m, Matrix4.fromUniformScale(scale), m);
, where
* m
must be an affine matrix.
* This function performs fewer allocations and arithmetic operations.
*
* @param {Matrix4} matrix The affine matrix on the left-hand side.
* @param {Cartesian3} scale The non-uniform scale on the right-hand side.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
*
* @example
* // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromScale(scale), m);
* Cesium.Matrix4.multiplyByScale(m, scale, m);
*
* @see Matrix4.fromScale
* @see Matrix4.multiplyByUniformScale
*/
Matrix4.multiplyByScale = function(matrix, scale, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(scale)) {
throw new DeveloperError('scale is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var scaleX = scale.x;
var scaleY = scale.y;
var scaleZ = scale.z;
// Faster than Cartesian3.equals
if ((scaleX === 1.0) && (scaleY === 1.0) && (scaleZ === 1.0)) {
return Matrix4.clone(matrix, result);
}
result[0] = scaleX * matrix[0];
result[1] = scaleX * matrix[1];
result[2] = scaleX * matrix[2];
result[3] = 0.0;
result[4] = scaleY * matrix[4];
result[5] = scaleY * matrix[5];
result[6] = scaleY * matrix[6];
result[7] = 0.0;
result[8] = scaleZ * matrix[8];
result[9] = scaleZ * matrix[9];
result[10] = scaleZ * matrix[10];
result[11] = 0.0;
result[12] = matrix[12];
result[13] = matrix[13];
result[14] = matrix[14];
result[15] = 1.0;
return result;
};
/**
* Computes the product of a matrix and a column vector.
*
* @param {Matrix4} matrix The matrix.
* @param {Cartesian4} cartesian The vector.
* @param {Cartesian4} result The object onto which to store the result.
* @returns {Cartesian4} The modified result parameter.
*/
Matrix4.multiplyByVector = function(matrix, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var vX = cartesian.x;
var vY = cartesian.y;
var vZ = cartesian.z;
var vW = cartesian.w;
var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12] * vW;
var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13] * vW;
var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14] * vW;
var w = matrix[3] * vX + matrix[7] * vY + matrix[11] * vZ + matrix[15] * vW;
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
/**
* Computes the product of a matrix and a {@link Cartesian3}. This is equivalent to calling {@link Matrix4.multiplyByVector}
* with a {@link Cartesian4} with a w
component of zero.
*
* @param {Matrix4} matrix The matrix.
* @param {Cartesian3} cartesian The point.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*
* @example
* var p = new Cesium.Cartesian3(1.0, 2.0, 3.0);
* var result = Cesium.Matrix4.multiplyByPointAsVector(matrix, p, new Cesium.Cartesian3());
* // A shortcut for
* // Cartesian3 p = ...
* // Cesium.Matrix4.multiplyByVector(matrix, new Cesium.Cartesian4(p.x, p.y, p.z, 0.0), result);
*/
Matrix4.multiplyByPointAsVector = function(matrix, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var vX = cartesian.x;
var vY = cartesian.y;
var vZ = cartesian.z;
var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ;
var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ;
var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ;
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Computes the product of a matrix and a {@link Cartesian3}. This is equivalent to calling {@link Matrix4.multiplyByVector}
* with a {@link Cartesian4} with a w
component of 1, but returns a {@link Cartesian3} instead of a {@link Cartesian4}.
*
* @param {Matrix4} matrix The matrix.
* @param {Cartesian3} cartesian The point.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*
* @example
* var p = new Cesium.Cartesian3(1.0, 2.0, 3.0);
* var result = Cesium.Matrix4.multiplyByPoint(matrix, p, new Cesium.Cartesian3());
*/
Matrix4.multiplyByPoint = function(matrix, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var vX = cartesian.x;
var vY = cartesian.y;
var vZ = cartesian.z;
var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12];
var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13];
var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14];
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Computes the product of a matrix and a scalar.
*
* @param {Matrix4} matrix The matrix.
* @param {Number} scalar The number to multiply by.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @example
* //create a Matrix4 instance which is a scaled version of the supplied Matrix4
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* var a = Cesium.Matrix4.multiplyByScalar(m, -2, new Cesium.Matrix4());
*
* // m remains the same
* // a = [-20.0, -22.0, -24.0, -26.0]
* // [-28.0, -30.0, -32.0, -34.0]
* // [-36.0, -38.0, -40.0, -42.0]
* // [-44.0, -46.0, -48.0, -50.0]
*/
Matrix4.multiplyByScalar = function(matrix, scalar, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar must be a number');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0] * scalar;
result[1] = matrix[1] * scalar;
result[2] = matrix[2] * scalar;
result[3] = matrix[3] * scalar;
result[4] = matrix[4] * scalar;
result[5] = matrix[5] * scalar;
result[6] = matrix[6] * scalar;
result[7] = matrix[7] * scalar;
result[8] = matrix[8] * scalar;
result[9] = matrix[9] * scalar;
result[10] = matrix[10] * scalar;
result[11] = matrix[11] * scalar;
result[12] = matrix[12] * scalar;
result[13] = matrix[13] * scalar;
result[14] = matrix[14] * scalar;
result[15] = matrix[15] * scalar;
return result;
};
/**
* Computes a negated copy of the provided matrix.
*
* @param {Matrix4} matrix The matrix to negate.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @example
* //create a new Matrix4 instance which is a negation of a Matrix4
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* var a = Cesium.Matrix4.negate(m, new Cesium.Matrix4());
*
* // m remains the same
* // a = [-10.0, -11.0, -12.0, -13.0]
* // [-14.0, -15.0, -16.0, -17.0]
* // [-18.0, -19.0, -20.0, -21.0]
* // [-22.0, -23.0, -24.0, -25.0]
*/
Matrix4.negate = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = -matrix[0];
result[1] = -matrix[1];
result[2] = -matrix[2];
result[3] = -matrix[3];
result[4] = -matrix[4];
result[5] = -matrix[5];
result[6] = -matrix[6];
result[7] = -matrix[7];
result[8] = -matrix[8];
result[9] = -matrix[9];
result[10] = -matrix[10];
result[11] = -matrix[11];
result[12] = -matrix[12];
result[13] = -matrix[13];
result[14] = -matrix[14];
result[15] = -matrix[15];
return result;
};
/**
* Computes the transpose of the provided matrix.
*
* @param {Matrix4} matrix The matrix to transpose.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @example
* //returns transpose of a Matrix4
* // m = [10.0, 11.0, 12.0, 13.0]
* // [14.0, 15.0, 16.0, 17.0]
* // [18.0, 19.0, 20.0, 21.0]
* // [22.0, 23.0, 24.0, 25.0]
*
* var a = Cesium.Matrix4.transpose(m, new Cesium.Matrix4());
*
* // m remains the same
* // a = [10.0, 14.0, 18.0, 22.0]
* // [11.0, 15.0, 19.0, 23.0]
* // [12.0, 16.0, 20.0, 24.0]
* // [13.0, 17.0, 21.0, 25.0]
*/
Matrix4.transpose = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var matrix1 = matrix[1];
var matrix2 = matrix[2];
var matrix3 = matrix[3];
var matrix6 = matrix[6];
var matrix7 = matrix[7];
var matrix11 = matrix[11];
result[0] = matrix[0];
result[1] = matrix[4];
result[2] = matrix[8];
result[3] = matrix[12];
result[4] = matrix1;
result[5] = matrix[5];
result[6] = matrix[9];
result[7] = matrix[13];
result[8] = matrix2;
result[9] = matrix6;
result[10] = matrix[10];
result[11] = matrix[14];
result[12] = matrix3;
result[13] = matrix7;
result[14] = matrix11;
result[15] = matrix[15];
return result;
};
/**
* Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
*
* @param {Matrix4} matrix The matrix with signed elements.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.abs = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = Math.abs(matrix[0]);
result[1] = Math.abs(matrix[1]);
result[2] = Math.abs(matrix[2]);
result[3] = Math.abs(matrix[3]);
result[4] = Math.abs(matrix[4]);
result[5] = Math.abs(matrix[5]);
result[6] = Math.abs(matrix[6]);
result[7] = Math.abs(matrix[7]);
result[8] = Math.abs(matrix[8]);
result[9] = Math.abs(matrix[9]);
result[10] = Math.abs(matrix[10]);
result[11] = Math.abs(matrix[11]);
result[12] = Math.abs(matrix[12]);
result[13] = Math.abs(matrix[13]);
result[14] = Math.abs(matrix[14]);
result[15] = Math.abs(matrix[15]);
return result;
};
/**
* Compares the provided matrices componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Matrix4} [left] The first matrix.
* @param {Matrix4} [right] The second matrix.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*
* @example
* //compares two Matrix4 instances
*
* // a = [10.0, 14.0, 18.0, 22.0]
* // [11.0, 15.0, 19.0, 23.0]
* // [12.0, 16.0, 20.0, 24.0]
* // [13.0, 17.0, 21.0, 25.0]
*
* // b = [10.0, 14.0, 18.0, 22.0]
* // [11.0, 15.0, 19.0, 23.0]
* // [12.0, 16.0, 20.0, 24.0]
* // [13.0, 17.0, 21.0, 25.0]
*
* if(Cesium.Matrix4.equals(a,b)) {
* console.log("Both matrices are equal");
* } else {
* console.log("They are not equal");
* }
*
* //Prints "Both matrices are equal" on the console
*/
Matrix4.equals = function(left, right) {
// Given that most matrices will be transformation matrices, the elements
// are tested in order such that the test is likely to fail as early
// as possible. I _think_ this is just as friendly to the L1 cache
// as testing in index order. It is certainty faster in practice.
return (left === right) ||
(defined(left) &&
defined(right) &&
// Translation
left[12] === right[12] &&
left[13] === right[13] &&
left[14] === right[14] &&
// Rotation/scale
left[0] === right[0] &&
left[1] === right[1] &&
left[2] === right[2] &&
left[4] === right[4] &&
left[5] === right[5] &&
left[6] === right[6] &&
left[8] === right[8] &&
left[9] === right[9] &&
left[10] === right[10] &&
// Bottom row
left[3] === right[3] &&
left[7] === right[7] &&
left[11] === right[11] &&
left[15] === right[15]);
};
/**
* Compares the provided matrices componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Matrix4} [left] The first matrix.
* @param {Matrix4} [right] The second matrix.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*
* @example
* //compares two Matrix4 instances
*
* // a = [10.5, 14.5, 18.5, 22.5]
* // [11.5, 15.5, 19.5, 23.5]
* // [12.5, 16.5, 20.5, 24.5]
* // [13.5, 17.5, 21.5, 25.5]
*
* // b = [10.0, 14.0, 18.0, 22.0]
* // [11.0, 15.0, 19.0, 23.0]
* // [12.0, 16.0, 20.0, 24.0]
* // [13.0, 17.0, 21.0, 25.0]
*
* if(Cesium.Matrix4.equalsEpsilon(a,b,0.1)){
* console.log("Difference between both the matrices is less than 0.1");
* } else {
* console.log("Difference between both the matrices is not less than 0.1");
* }
*
* //Prints "Difference between both the matrices is not less than 0.1" on the console
*/
Matrix4.equalsEpsilon = function(left, right, epsilon) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon must be a number');
}
return (left === right) ||
(defined(left) &&
defined(right) &&
Math.abs(left[0] - right[0]) <= epsilon &&
Math.abs(left[1] - right[1]) <= epsilon &&
Math.abs(left[2] - right[2]) <= epsilon &&
Math.abs(left[3] - right[3]) <= epsilon &&
Math.abs(left[4] - right[4]) <= epsilon &&
Math.abs(left[5] - right[5]) <= epsilon &&
Math.abs(left[6] - right[6]) <= epsilon &&
Math.abs(left[7] - right[7]) <= epsilon &&
Math.abs(left[8] - right[8]) <= epsilon &&
Math.abs(left[9] - right[9]) <= epsilon &&
Math.abs(left[10] - right[10]) <= epsilon &&
Math.abs(left[11] - right[11]) <= epsilon &&
Math.abs(left[12] - right[12]) <= epsilon &&
Math.abs(left[13] - right[13]) <= epsilon &&
Math.abs(left[14] - right[14]) <= epsilon &&
Math.abs(left[15] - right[15]) <= epsilon);
};
/**
* Gets the translation portion of the provided matrix, assuming the matrix is a affine transformation matrix.
*
* @param {Matrix4} matrix The matrix to use.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Matrix4.getTranslation = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = matrix[12];
result.y = matrix[13];
result.z = matrix[14];
return result;
};
/**
* Gets the upper left 3x3 rotation matrix of the provided matrix, assuming the matrix is a affine transformation matrix.
*
* @param {Matrix4} matrix The matrix to use.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*
* @example
* // returns a Matrix3 instance from a Matrix4 instance
*
* // m = [10.0, 14.0, 18.0, 22.0]
* // [11.0, 15.0, 19.0, 23.0]
* // [12.0, 16.0, 20.0, 24.0]
* // [13.0, 17.0, 21.0, 25.0]
*
* var b = new Cesium.Matrix3();
* Cesium.Matrix4.getRotation(m,b);
*
* // b = [10.0, 14.0, 18.0]
* // [11.0, 15.0, 19.0]
* // [12.0, 16.0, 20.0]
*/
Matrix4.getRotation = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[4];
result[4] = matrix[5];
result[5] = matrix[6];
result[6] = matrix[8];
result[7] = matrix[9];
result[8] = matrix[10];
return result;
};
var scratchInverseRotation = new Matrix3();
var scratchMatrix3Zero = new Matrix3();
var scratchBottomRow = new Cartesian4();
var scratchExpectedBottomRow = new Cartesian4(0.0, 0.0, 0.0, 1.0);
/**
* Computes the inverse of the provided matrix using Cramers Rule.
* If the determinant is zero, the matrix can not be inverted, and an exception is thrown.
* If the matrix is an affine transformation matrix, it is more efficient
* to invert it with {@link Matrix4.inverseTransformation}.
*
* @param {Matrix4} matrix The matrix to invert.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*
* @exception {RuntimeError} matrix is not invertible because its determinate is zero.
*/
Matrix4.inverse = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
// Special case for a zero scale matrix that can occur, for example,
// when a model's node has a [0, 0, 0] scale.
if (Matrix3.equalsEpsilon(Matrix4.getRotation(matrix, scratchInverseRotation), scratchMatrix3Zero, CesiumMath.EPSILON7) &&
Cartesian4.equals(Matrix4.getRow(matrix, 3, scratchBottomRow), scratchExpectedBottomRow)) {
result[0] = 0.0;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = 0.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = 0.0;
result[11] = 0.0;
result[12] = -matrix[12];
result[13] = -matrix[13];
result[14] = -matrix[14];
result[15] = 1.0;
return result;
}
//
// Ported from:
// ftp://download.intel.com/design/PentiumIII/sml/24504301.pdf
//
var src0 = matrix[0];
var src1 = matrix[4];
var src2 = matrix[8];
var src3 = matrix[12];
var src4 = matrix[1];
var src5 = matrix[5];
var src6 = matrix[9];
var src7 = matrix[13];
var src8 = matrix[2];
var src9 = matrix[6];
var src10 = matrix[10];
var src11 = matrix[14];
var src12 = matrix[3];
var src13 = matrix[7];
var src14 = matrix[11];
var src15 = matrix[15];
// calculate pairs for first 8 elements (cofactors)
var tmp0 = src10 * src15;
var tmp1 = src11 * src14;
var tmp2 = src9 * src15;
var tmp3 = src11 * src13;
var tmp4 = src9 * src14;
var tmp5 = src10 * src13;
var tmp6 = src8 * src15;
var tmp7 = src11 * src12;
var tmp8 = src8 * src14;
var tmp9 = src10 * src12;
var tmp10 = src8 * src13;
var tmp11 = src9 * src12;
// calculate first 8 elements (cofactors)
var dst0 = (tmp0 * src5 + tmp3 * src6 + tmp4 * src7) - (tmp1 * src5 + tmp2 * src6 + tmp5 * src7);
var dst1 = (tmp1 * src4 + tmp6 * src6 + tmp9 * src7) - (tmp0 * src4 + tmp7 * src6 + tmp8 * src7);
var dst2 = (tmp2 * src4 + tmp7 * src5 + tmp10 * src7) - (tmp3 * src4 + tmp6 * src5 + tmp11 * src7);
var dst3 = (tmp5 * src4 + tmp8 * src5 + tmp11 * src6) - (tmp4 * src4 + tmp9 * src5 + tmp10 * src6);
var dst4 = (tmp1 * src1 + tmp2 * src2 + tmp5 * src3) - (tmp0 * src1 + tmp3 * src2 + tmp4 * src3);
var dst5 = (tmp0 * src0 + tmp7 * src2 + tmp8 * src3) - (tmp1 * src0 + tmp6 * src2 + tmp9 * src3);
var dst6 = (tmp3 * src0 + tmp6 * src1 + tmp11 * src3) - (tmp2 * src0 + tmp7 * src1 + tmp10 * src3);
var dst7 = (tmp4 * src0 + tmp9 * src1 + tmp10 * src2) - (tmp5 * src0 + tmp8 * src1 + tmp11 * src2);
// calculate pairs for second 8 elements (cofactors)
tmp0 = src2 * src7;
tmp1 = src3 * src6;
tmp2 = src1 * src7;
tmp3 = src3 * src5;
tmp4 = src1 * src6;
tmp5 = src2 * src5;
tmp6 = src0 * src7;
tmp7 = src3 * src4;
tmp8 = src0 * src6;
tmp9 = src2 * src4;
tmp10 = src0 * src5;
tmp11 = src1 * src4;
// calculate second 8 elements (cofactors)
var dst8 = (tmp0 * src13 + tmp3 * src14 + tmp4 * src15) - (tmp1 * src13 + tmp2 * src14 + tmp5 * src15);
var dst9 = (tmp1 * src12 + tmp6 * src14 + tmp9 * src15) - (tmp0 * src12 + tmp7 * src14 + tmp8 * src15);
var dst10 = (tmp2 * src12 + tmp7 * src13 + tmp10 * src15) - (tmp3 * src12 + tmp6 * src13 + tmp11 * src15);
var dst11 = (tmp5 * src12 + tmp8 * src13 + tmp11 * src14) - (tmp4 * src12 + tmp9 * src13 + tmp10 * src14);
var dst12 = (tmp2 * src10 + tmp5 * src11 + tmp1 * src9) - (tmp4 * src11 + tmp0 * src9 + tmp3 * src10);
var dst13 = (tmp8 * src11 + tmp0 * src8 + tmp7 * src10) - (tmp6 * src10 + tmp9 * src11 + tmp1 * src8);
var dst14 = (tmp6 * src9 + tmp11 * src11 + tmp3 * src8) - (tmp10 * src11 + tmp2 * src8 + tmp7 * src9);
var dst15 = (tmp10 * src10 + tmp4 * src8 + tmp9 * src9) - (tmp8 * src9 + tmp11 * src10 + tmp5 * src8);
// calculate determinant
var det = src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3;
if (Math.abs(det) < CesiumMath.EPSILON20) {
throw new RuntimeError('matrix is not invertible because its determinate is zero.');
}
// calculate matrix inverse
det = 1.0 / det;
result[0] = dst0 * det;
result[1] = dst1 * det;
result[2] = dst2 * det;
result[3] = dst3 * det;
result[4] = dst4 * det;
result[5] = dst5 * det;
result[6] = dst6 * det;
result[7] = dst7 * det;
result[8] = dst8 * det;
result[9] = dst9 * det;
result[10] = dst10 * det;
result[11] = dst11 * det;
result[12] = dst12 * det;
result[13] = dst13 * det;
result[14] = dst14 * det;
result[15] = dst15 * det;
return result;
};
/**
* Computes the inverse of the provided matrix assuming it is
* an affine transformation matrix, where the upper left 3x3 elements
* are a rotation matrix, and the upper three elements in the fourth
* column are the translation. The bottom row is assumed to be [0, 0, 0, 1].
* The matrix is not verified to be in the proper form.
* This method is faster than computing the inverse for a general 4x4
* matrix using {@link Matrix4.inverse}.
*
* @param {Matrix4} matrix The matrix to invert.
* @param {Matrix4} result The object onto which to store the result.
* @returns {Matrix4} The modified result parameter.
*/
Matrix4.inverseTransformation = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
//This function is an optimized version of the below 4 lines.
//var rT = Matrix3.transpose(Matrix4.getRotation(matrix));
//var rTN = Matrix3.negate(rT);
//var rTT = Matrix3.multiplyByVector(rTN, Matrix4.getTranslation(matrix));
//return Matrix4.fromRotationTranslation(rT, rTT, result);
var matrix0 = matrix[0];
var matrix1 = matrix[1];
var matrix2 = matrix[2];
var matrix4 = matrix[4];
var matrix5 = matrix[5];
var matrix6 = matrix[6];
var matrix8 = matrix[8];
var matrix9 = matrix[9];
var matrix10 = matrix[10];
var vX = matrix[12];
var vY = matrix[13];
var vZ = matrix[14];
var x = -matrix0 * vX - matrix1 * vY - matrix2 * vZ;
var y = -matrix4 * vX - matrix5 * vY - matrix6 * vZ;
var z = -matrix8 * vX - matrix9 * vY - matrix10 * vZ;
result[0] = matrix0;
result[1] = matrix4;
result[2] = matrix8;
result[3] = 0.0;
result[4] = matrix1;
result[5] = matrix5;
result[6] = matrix9;
result[7] = 0.0;
result[8] = matrix2;
result[9] = matrix6;
result[10] = matrix10;
result[11] = 0.0;
result[12] = x;
result[13] = y;
result[14] = z;
result[15] = 1.0;
return result;
};
/**
* An immutable Matrix4 instance initialized to the identity matrix.
*
* @type {Matrix4}
* @constant
*/
Matrix4.IDENTITY = freezeObject(new Matrix4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0));
/**
* An immutable Matrix4 instance initialized to the zero matrix.
*
* @type {Matrix4}
* @constant
*/
Matrix4.ZERO = freezeObject(new Matrix4(0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0));
/**
* The index into Matrix4 for column 0, row 0.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN0ROW0 = 0;
/**
* The index into Matrix4 for column 0, row 1.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN0ROW1 = 1;
/**
* The index into Matrix4 for column 0, row 2.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN0ROW2 = 2;
/**
* The index into Matrix4 for column 0, row 3.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN0ROW3 = 3;
/**
* The index into Matrix4 for column 1, row 0.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN1ROW0 = 4;
/**
* The index into Matrix4 for column 1, row 1.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN1ROW1 = 5;
/**
* The index into Matrix4 for column 1, row 2.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN1ROW2 = 6;
/**
* The index into Matrix4 for column 1, row 3.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN1ROW3 = 7;
/**
* The index into Matrix4 for column 2, row 0.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN2ROW0 = 8;
/**
* The index into Matrix4 for column 2, row 1.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN2ROW1 = 9;
/**
* The index into Matrix4 for column 2, row 2.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN2ROW2 = 10;
/**
* The index into Matrix4 for column 2, row 3.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN2ROW3 = 11;
/**
* The index into Matrix4 for column 3, row 0.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN3ROW0 = 12;
/**
* The index into Matrix4 for column 3, row 1.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN3ROW1 = 13;
/**
* The index into Matrix4 for column 3, row 2.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN3ROW2 = 14;
/**
* The index into Matrix4 for column 3, row 3.
*
* @type {Number}
* @constant
*/
Matrix4.COLUMN3ROW3 = 15;
defineProperties(Matrix4.prototype, {
/**
* Gets the number of items in the collection.
* @memberof Matrix4.prototype
*
* @type {Number}
*/
length : {
get : function() {
return Matrix4.packedLength;
}
}
});
/**
* Duplicates the provided Matrix4 instance.
*
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
*/
Matrix4.prototype.clone = function(result) {
return Matrix4.clone(this, result);
};
/**
* Compares this matrix to the provided matrix componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Matrix4} [right] The right hand side matrix.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Matrix4.prototype.equals = function(right) {
return Matrix4.equals(this, right);
};
/**
* @private
*/
Matrix4.equalsArray = function(matrix, array, offset) {
return matrix[0] === array[offset] &&
matrix[1] === array[offset + 1] &&
matrix[2] === array[offset + 2] &&
matrix[3] === array[offset + 3] &&
matrix[4] === array[offset + 4] &&
matrix[5] === array[offset + 5] &&
matrix[6] === array[offset + 6] &&
matrix[7] === array[offset + 7] &&
matrix[8] === array[offset + 8] &&
matrix[9] === array[offset + 9] &&
matrix[10] === array[offset + 10] &&
matrix[11] === array[offset + 11] &&
matrix[12] === array[offset + 12] &&
matrix[13] === array[offset + 13] &&
matrix[14] === array[offset + 14] &&
matrix[15] === array[offset + 15];
};
/**
* Compares this matrix to the provided matrix componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Matrix4} [right] The right hand side matrix.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
Matrix4.prototype.equalsEpsilon = function(right, epsilon) {
return Matrix4.equalsEpsilon(this, right, epsilon);
};
/**
* Computes a string representing this Matrix with each row being
* on a separate line and in the format '(column0, column1, column2, column3)'.
*
* @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1, column2, column3)'.
*/
Matrix4.prototype.toString = function() {
return '(' + this[0] + ', ' + this[4] + ', ' + this[8] + ', ' + this[12] +')\n' +
'(' + this[1] + ', ' + this[5] + ', ' + this[9] + ', ' + this[13] +')\n' +
'(' + this[2] + ', ' + this[6] + ', ' + this[10] + ', ' + this[14] +')\n' +
'(' + this[3] + ', ' + this[7] + ', ' + this[11] + ', ' + this[15] +')';
};
return Matrix4;
});
/*global define*/
define('Core/BoundingSphere',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./GeographicProjection',
'./Intersect',
'./Interval',
'./Matrix3',
'./Matrix4',
'./Rectangle'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
GeographicProjection,
Intersect,
Interval,
Matrix3,
Matrix4,
Rectangle) {
'use strict';
/**
* A bounding sphere with a center and a radius.
* @alias BoundingSphere
* @constructor
*
* @param {Cartesian3} [center=Cartesian3.ZERO] The center of the bounding sphere.
* @param {Number} [radius=0.0] The radius of the bounding sphere.
*
* @see AxisAlignedBoundingBox
* @see BoundingRectangle
* @see Packable
*/
function BoundingSphere(center, radius) {
/**
* The center point of the sphere.
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO));
/**
* The radius of the sphere.
* @type {Number}
* @default 0.0
*/
this.radius = defaultValue(radius, 0.0);
}
var fromPointsXMin = new Cartesian3();
var fromPointsYMin = new Cartesian3();
var fromPointsZMin = new Cartesian3();
var fromPointsXMax = new Cartesian3();
var fromPointsYMax = new Cartesian3();
var fromPointsZMax = new Cartesian3();
var fromPointsCurrentPos = new Cartesian3();
var fromPointsScratch = new Cartesian3();
var fromPointsRitterCenter = new Cartesian3();
var fromPointsMinBoxPt = new Cartesian3();
var fromPointsMaxBoxPt = new Cartesian3();
var fromPointsNaiveCenterScratch = new Cartesian3();
/**
* Computes a tight-fitting bounding sphere enclosing a list of 3D Cartesian points.
* The bounding sphere is computed by running two algorithms, a naive algorithm and
* Ritter's algorithm. The smaller of the two spheres is used to ensure a tight fit.
*
* @param {Cartesian3[]} positions An array of points that the bounding sphere will enclose. Each point must have x
, y
, and z
properties.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
*
* @see {@link http://blogs.agi.com/insight3d/index.php/2008/02/04/a-bounding/|Bounding Sphere computation article}
*/
BoundingSphere.fromPoints = function(positions, result) {
if (!defined(result)) {
result = new BoundingSphere();
}
if (!defined(positions) || positions.length === 0) {
result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
result.radius = 0.0;
return result;
}
var currentPos = Cartesian3.clone(positions[0], fromPointsCurrentPos);
var xMin = Cartesian3.clone(currentPos, fromPointsXMin);
var yMin = Cartesian3.clone(currentPos, fromPointsYMin);
var zMin = Cartesian3.clone(currentPos, fromPointsZMin);
var xMax = Cartesian3.clone(currentPos, fromPointsXMax);
var yMax = Cartesian3.clone(currentPos, fromPointsYMax);
var zMax = Cartesian3.clone(currentPos, fromPointsZMax);
var numPositions = positions.length;
for (var i = 1; i < numPositions; i++) {
Cartesian3.clone(positions[i], currentPos);
var x = currentPos.x;
var y = currentPos.y;
var z = currentPos.z;
// Store points containing the the smallest and largest components
if (x < xMin.x) {
Cartesian3.clone(currentPos, xMin);
}
if (x > xMax.x) {
Cartesian3.clone(currentPos, xMax);
}
if (y < yMin.y) {
Cartesian3.clone(currentPos, yMin);
}
if (y > yMax.y) {
Cartesian3.clone(currentPos, yMax);
}
if (z < zMin.z) {
Cartesian3.clone(currentPos, zMin);
}
if (z > zMax.z) {
Cartesian3.clone(currentPos, zMax);
}
}
// Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.).
var xSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(xMax, xMin, fromPointsScratch));
var ySpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(yMax, yMin, fromPointsScratch));
var zSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(zMax, zMin, fromPointsScratch));
// Set the diameter endpoints to the largest span.
var diameter1 = xMin;
var diameter2 = xMax;
var maxSpan = xSpan;
if (ySpan > maxSpan) {
maxSpan = ySpan;
diameter1 = yMin;
diameter2 = yMax;
}
if (zSpan > maxSpan) {
maxSpan = zSpan;
diameter1 = zMin;
diameter2 = zMax;
}
// Calculate the center of the initial sphere found by Ritter's algorithm
var ritterCenter = fromPointsRitterCenter;
ritterCenter.x = (diameter1.x + diameter2.x) * 0.5;
ritterCenter.y = (diameter1.y + diameter2.y) * 0.5;
ritterCenter.z = (diameter1.z + diameter2.z) * 0.5;
// Calculate the radius of the initial sphere found by Ritter's algorithm
var radiusSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(diameter2, ritterCenter, fromPointsScratch));
var ritterRadius = Math.sqrt(radiusSquared);
// Find the center of the sphere found using the Naive method.
var minBoxPt = fromPointsMinBoxPt;
minBoxPt.x = xMin.x;
minBoxPt.y = yMin.y;
minBoxPt.z = zMin.z;
var maxBoxPt = fromPointsMaxBoxPt;
maxBoxPt.x = xMax.x;
maxBoxPt.y = yMax.y;
maxBoxPt.z = zMax.z;
var naiveCenter = Cartesian3.multiplyByScalar(Cartesian3.add(minBoxPt, maxBoxPt, fromPointsScratch), 0.5, fromPointsNaiveCenterScratch);
// Begin 2nd pass to find naive radius and modify the ritter sphere.
var naiveRadius = 0;
for (i = 0; i < numPositions; i++) {
Cartesian3.clone(positions[i], currentPos);
// Find the furthest point from the naive center to calculate the naive radius.
var r = Cartesian3.magnitude(Cartesian3.subtract(currentPos, naiveCenter, fromPointsScratch));
if (r > naiveRadius) {
naiveRadius = r;
}
// Make adjustments to the Ritter Sphere to include all points.
var oldCenterToPointSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(currentPos, ritterCenter, fromPointsScratch));
if (oldCenterToPointSquared > radiusSquared) {
var oldCenterToPoint = Math.sqrt(oldCenterToPointSquared);
// Calculate new radius to include the point that lies outside
ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5;
radiusSquared = ritterRadius * ritterRadius;
// Calculate center of new Ritter sphere
var oldToNew = oldCenterToPoint - ritterRadius;
ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint;
ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint;
ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint;
}
}
if (ritterRadius < naiveRadius) {
Cartesian3.clone(ritterCenter, result.center);
result.radius = ritterRadius;
} else {
Cartesian3.clone(naiveCenter, result.center);
result.radius = naiveRadius;
}
return result;
};
var defaultProjection = new GeographicProjection();
var fromRectangle2DLowerLeft = new Cartesian3();
var fromRectangle2DUpperRight = new Cartesian3();
var fromRectangle2DSouthwest = new Cartographic();
var fromRectangle2DNortheast = new Cartographic();
/**
* Computes a bounding sphere from an rectangle projected in 2D.
*
* @param {Rectangle} rectangle The rectangle around which to create a bounding sphere.
* @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.fromRectangle2D = function(rectangle, projection, result) {
return BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, 0.0, 0.0, result);
};
/**
* Computes a bounding sphere from an rectangle projected in 2D. The bounding sphere accounts for the
* object's minimum and maximum heights over the rectangle.
*
* @param {Rectangle} rectangle The rectangle around which to create a bounding sphere.
* @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
* @param {Number} [minimumHeight=0.0] The minimum height over the rectangle.
* @param {Number} [maximumHeight=0.0] The maximum height over the rectangle.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.fromRectangleWithHeights2D = function(rectangle, projection, minimumHeight, maximumHeight, result) {
if (!defined(result)) {
result = new BoundingSphere();
}
if (!defined(rectangle)) {
result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
result.radius = 0.0;
return result;
}
projection = defaultValue(projection, defaultProjection);
Rectangle.southwest(rectangle, fromRectangle2DSouthwest);
fromRectangle2DSouthwest.height = minimumHeight;
Rectangle.northeast(rectangle, fromRectangle2DNortheast);
fromRectangle2DNortheast.height = maximumHeight;
var lowerLeft = projection.project(fromRectangle2DSouthwest, fromRectangle2DLowerLeft);
var upperRight = projection.project(fromRectangle2DNortheast, fromRectangle2DUpperRight);
var width = upperRight.x - lowerLeft.x;
var height = upperRight.y - lowerLeft.y;
var elevation = upperRight.z - lowerLeft.z;
result.radius = Math.sqrt(width * width + height * height + elevation * elevation) * 0.5;
var center = result.center;
center.x = lowerLeft.x + width * 0.5;
center.y = lowerLeft.y + height * 0.5;
center.z = lowerLeft.z + elevation * 0.5;
return result;
};
var fromRectangle3DScratch = [];
/**
* Computes a bounding sphere from an rectangle in 3D. The bounding sphere is created using a subsample of points
* on the ellipsoid and contained in the rectangle. It may not be accurate for all rectangles on all types of ellipsoids.
*
* @param {Rectangle} rectangle The valid rectangle used to create a bounding sphere.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the rectangle.
* @param {Number} [surfaceHeight=0.0] The height above the surface of the ellipsoid.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.fromRectangle3D = function(rectangle, ellipsoid, surfaceHeight, result) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
surfaceHeight = defaultValue(surfaceHeight, 0.0);
var positions;
if (defined(rectangle)) {
positions = Rectangle.subsample(rectangle, ellipsoid, surfaceHeight, fromRectangle3DScratch);
}
return BoundingSphere.fromPoints(positions, result);
};
/**
* Computes a tight-fitting bounding sphere enclosing a list of 3D points, where the points are
* stored in a flat array in X, Y, Z, order. The bounding sphere is computed by running two
* algorithms, a naive algorithm and Ritter's algorithm. The smaller of the two spheres is used to
* ensure a tight fit.
*
* @param {Number[]} positions An array of points that the bounding sphere will enclose. Each point
* is formed from three elements in the array in the order X, Y, Z.
* @param {Cartesian3} [center=Cartesian3.ZERO] The position to which the positions are relative, which need not be the
* origin of the coordinate system. This is useful when the positions are to be used for
* relative-to-center (RTC) rendering.
* @param {Number} [stride=3] The number of array elements per vertex. It must be at least 3, but it may
* be higher. Regardless of the value of this parameter, the X coordinate of the first position
* is at array index 0, the Y coordinate is at array index 1, and the Z coordinate is at array index
* 2. When stride is 3, the X coordinate of the next position then begins at array index 3. If
* the stride is 5, however, two array elements are skipped and the next position begins at array
* index 5.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
*
* @example
* // Compute the bounding sphere from 3 positions, each specified relative to a center.
* // In addition to the X, Y, and Z coordinates, the points array contains two additional
* // elements per point which are ignored for the purpose of computing the bounding sphere.
* var center = new Cesium.Cartesian3(1.0, 2.0, 3.0);
* var points = [1.0, 2.0, 3.0, 0.1, 0.2,
* 4.0, 5.0, 6.0, 0.1, 0.2,
* 7.0, 8.0, 9.0, 0.1, 0.2];
* var sphere = Cesium.BoundingSphere.fromVertices(points, center, 5);
*
* @see {@link http://blogs.agi.com/insight3d/index.php/2008/02/04/a-bounding/|Bounding Sphere computation article}
*/
BoundingSphere.fromVertices = function(positions, center, stride, result) {
if (!defined(result)) {
result = new BoundingSphere();
}
if (!defined(positions) || positions.length === 0) {
result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
result.radius = 0.0;
return result;
}
center = defaultValue(center, Cartesian3.ZERO);
stride = defaultValue(stride, 3);
if (stride < 3) {
throw new DeveloperError('stride must be 3 or greater.');
}
var currentPos = fromPointsCurrentPos;
currentPos.x = positions[0] + center.x;
currentPos.y = positions[1] + center.y;
currentPos.z = positions[2] + center.z;
var xMin = Cartesian3.clone(currentPos, fromPointsXMin);
var yMin = Cartesian3.clone(currentPos, fromPointsYMin);
var zMin = Cartesian3.clone(currentPos, fromPointsZMin);
var xMax = Cartesian3.clone(currentPos, fromPointsXMax);
var yMax = Cartesian3.clone(currentPos, fromPointsYMax);
var zMax = Cartesian3.clone(currentPos, fromPointsZMax);
var numElements = positions.length;
for (var i = 0; i < numElements; i += stride) {
var x = positions[i] + center.x;
var y = positions[i + 1] + center.y;
var z = positions[i + 2] + center.z;
currentPos.x = x;
currentPos.y = y;
currentPos.z = z;
// Store points containing the the smallest and largest components
if (x < xMin.x) {
Cartesian3.clone(currentPos, xMin);
}
if (x > xMax.x) {
Cartesian3.clone(currentPos, xMax);
}
if (y < yMin.y) {
Cartesian3.clone(currentPos, yMin);
}
if (y > yMax.y) {
Cartesian3.clone(currentPos, yMax);
}
if (z < zMin.z) {
Cartesian3.clone(currentPos, zMin);
}
if (z > zMax.z) {
Cartesian3.clone(currentPos, zMax);
}
}
// Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.).
var xSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(xMax, xMin, fromPointsScratch));
var ySpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(yMax, yMin, fromPointsScratch));
var zSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(zMax, zMin, fromPointsScratch));
// Set the diameter endpoints to the largest span.
var diameter1 = xMin;
var diameter2 = xMax;
var maxSpan = xSpan;
if (ySpan > maxSpan) {
maxSpan = ySpan;
diameter1 = yMin;
diameter2 = yMax;
}
if (zSpan > maxSpan) {
maxSpan = zSpan;
diameter1 = zMin;
diameter2 = zMax;
}
// Calculate the center of the initial sphere found by Ritter's algorithm
var ritterCenter = fromPointsRitterCenter;
ritterCenter.x = (diameter1.x + diameter2.x) * 0.5;
ritterCenter.y = (diameter1.y + diameter2.y) * 0.5;
ritterCenter.z = (diameter1.z + diameter2.z) * 0.5;
// Calculate the radius of the initial sphere found by Ritter's algorithm
var radiusSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(diameter2, ritterCenter, fromPointsScratch));
var ritterRadius = Math.sqrt(radiusSquared);
// Find the center of the sphere found using the Naive method.
var minBoxPt = fromPointsMinBoxPt;
minBoxPt.x = xMin.x;
minBoxPt.y = yMin.y;
minBoxPt.z = zMin.z;
var maxBoxPt = fromPointsMaxBoxPt;
maxBoxPt.x = xMax.x;
maxBoxPt.y = yMax.y;
maxBoxPt.z = zMax.z;
var naiveCenter = Cartesian3.multiplyByScalar(Cartesian3.add(minBoxPt, maxBoxPt, fromPointsScratch), 0.5, fromPointsNaiveCenterScratch);
// Begin 2nd pass to find naive radius and modify the ritter sphere.
var naiveRadius = 0;
for (i = 0; i < numElements; i += stride) {
currentPos.x = positions[i] + center.x;
currentPos.y = positions[i + 1] + center.y;
currentPos.z = positions[i + 2] + center.z;
// Find the furthest point from the naive center to calculate the naive radius.
var r = Cartesian3.magnitude(Cartesian3.subtract(currentPos, naiveCenter, fromPointsScratch));
if (r > naiveRadius) {
naiveRadius = r;
}
// Make adjustments to the Ritter Sphere to include all points.
var oldCenterToPointSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(currentPos, ritterCenter, fromPointsScratch));
if (oldCenterToPointSquared > radiusSquared) {
var oldCenterToPoint = Math.sqrt(oldCenterToPointSquared);
// Calculate new radius to include the point that lies outside
ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5;
radiusSquared = ritterRadius * ritterRadius;
// Calculate center of new Ritter sphere
var oldToNew = oldCenterToPoint - ritterRadius;
ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint;
ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint;
ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint;
}
}
if (ritterRadius < naiveRadius) {
Cartesian3.clone(ritterCenter, result.center);
result.radius = ritterRadius;
} else {
Cartesian3.clone(naiveCenter, result.center);
result.radius = naiveRadius;
}
return result;
};
/**
* Computes a tight-fitting bounding sphere enclosing a list of {@link EncodedCartesian3}s, where the points are
* stored in parallel flat arrays in X, Y, Z, order. The bounding sphere is computed by running two
* algorithms, a naive algorithm and Ritter's algorithm. The smaller of the two spheres is used to
* ensure a tight fit.
*
* @param {Number[]} positionsHigh An array of high bits of the encoded cartesians that the bounding sphere will enclose. Each point
* is formed from three elements in the array in the order X, Y, Z.
* @param {Number[]} positionsLow An array of low bits of the encoded cartesians that the bounding sphere will enclose. Each point
* is formed from three elements in the array in the order X, Y, Z.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
*
* @see {@link http://blogs.agi.com/insight3d/index.php/2008/02/04/a-bounding/|Bounding Sphere computation article}
*/
BoundingSphere.fromEncodedCartesianVertices = function(positionsHigh, positionsLow, result) {
if (!defined(result)) {
result = new BoundingSphere();
}
if (!defined(positionsHigh) || !defined(positionsLow) || positionsHigh.length !== positionsLow.length || positionsHigh.length === 0) {
result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
result.radius = 0.0;
return result;
}
var currentPos = fromPointsCurrentPos;
currentPos.x = positionsHigh[0] + positionsLow[0];
currentPos.y = positionsHigh[1] + positionsLow[1];
currentPos.z = positionsHigh[2] + positionsLow[2];
var xMin = Cartesian3.clone(currentPos, fromPointsXMin);
var yMin = Cartesian3.clone(currentPos, fromPointsYMin);
var zMin = Cartesian3.clone(currentPos, fromPointsZMin);
var xMax = Cartesian3.clone(currentPos, fromPointsXMax);
var yMax = Cartesian3.clone(currentPos, fromPointsYMax);
var zMax = Cartesian3.clone(currentPos, fromPointsZMax);
var numElements = positionsHigh.length;
for (var i = 0; i < numElements; i += 3) {
var x = positionsHigh[i] + positionsLow[i];
var y = positionsHigh[i + 1] + positionsLow[i + 1];
var z = positionsHigh[i + 2] + positionsLow[i + 2];
currentPos.x = x;
currentPos.y = y;
currentPos.z = z;
// Store points containing the the smallest and largest components
if (x < xMin.x) {
Cartesian3.clone(currentPos, xMin);
}
if (x > xMax.x) {
Cartesian3.clone(currentPos, xMax);
}
if (y < yMin.y) {
Cartesian3.clone(currentPos, yMin);
}
if (y > yMax.y) {
Cartesian3.clone(currentPos, yMax);
}
if (z < zMin.z) {
Cartesian3.clone(currentPos, zMin);
}
if (z > zMax.z) {
Cartesian3.clone(currentPos, zMax);
}
}
// Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.).
var xSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(xMax, xMin, fromPointsScratch));
var ySpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(yMax, yMin, fromPointsScratch));
var zSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(zMax, zMin, fromPointsScratch));
// Set the diameter endpoints to the largest span.
var diameter1 = xMin;
var diameter2 = xMax;
var maxSpan = xSpan;
if (ySpan > maxSpan) {
maxSpan = ySpan;
diameter1 = yMin;
diameter2 = yMax;
}
if (zSpan > maxSpan) {
maxSpan = zSpan;
diameter1 = zMin;
diameter2 = zMax;
}
// Calculate the center of the initial sphere found by Ritter's algorithm
var ritterCenter = fromPointsRitterCenter;
ritterCenter.x = (diameter1.x + diameter2.x) * 0.5;
ritterCenter.y = (diameter1.y + diameter2.y) * 0.5;
ritterCenter.z = (diameter1.z + diameter2.z) * 0.5;
// Calculate the radius of the initial sphere found by Ritter's algorithm
var radiusSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(diameter2, ritterCenter, fromPointsScratch));
var ritterRadius = Math.sqrt(radiusSquared);
// Find the center of the sphere found using the Naive method.
var minBoxPt = fromPointsMinBoxPt;
minBoxPt.x = xMin.x;
minBoxPt.y = yMin.y;
minBoxPt.z = zMin.z;
var maxBoxPt = fromPointsMaxBoxPt;
maxBoxPt.x = xMax.x;
maxBoxPt.y = yMax.y;
maxBoxPt.z = zMax.z;
var naiveCenter = Cartesian3.multiplyByScalar(Cartesian3.add(minBoxPt, maxBoxPt, fromPointsScratch), 0.5, fromPointsNaiveCenterScratch);
// Begin 2nd pass to find naive radius and modify the ritter sphere.
var naiveRadius = 0;
for (i = 0; i < numElements; i += 3) {
currentPos.x = positionsHigh[i] + positionsLow[i];
currentPos.y = positionsHigh[i + 1] + positionsLow[i + 1];
currentPos.z = positionsHigh[i + 2] + positionsLow[i + 2];
// Find the furthest point from the naive center to calculate the naive radius.
var r = Cartesian3.magnitude(Cartesian3.subtract(currentPos, naiveCenter, fromPointsScratch));
if (r > naiveRadius) {
naiveRadius = r;
}
// Make adjustments to the Ritter Sphere to include all points.
var oldCenterToPointSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(currentPos, ritterCenter, fromPointsScratch));
if (oldCenterToPointSquared > radiusSquared) {
var oldCenterToPoint = Math.sqrt(oldCenterToPointSquared);
// Calculate new radius to include the point that lies outside
ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5;
radiusSquared = ritterRadius * ritterRadius;
// Calculate center of new Ritter sphere
var oldToNew = oldCenterToPoint - ritterRadius;
ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint;
ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint;
ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint;
}
}
if (ritterRadius < naiveRadius) {
Cartesian3.clone(ritterCenter, result.center);
result.radius = ritterRadius;
} else {
Cartesian3.clone(naiveCenter, result.center);
result.radius = naiveRadius;
}
return result;
};
/**
* Computes a bounding sphere from the corner points of an axis-aligned bounding box. The sphere
* tighly and fully encompases the box.
*
* @param {Cartesian3} [corner] The minimum height over the rectangle.
* @param {Cartesian3} [oppositeCorner] The maximum height over the rectangle.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*
* @example
* // Create a bounding sphere around the unit cube
* var sphere = Cesium.BoundingSphere.fromCornerPoints(new Cesium.Cartesian3(-0.5, -0.5, -0.5), new Cesium.Cartesian3(0.5, 0.5, 0.5));
*/
BoundingSphere.fromCornerPoints = function(corner, oppositeCorner, result) {
if (!defined(corner) || !defined(oppositeCorner)) {
throw new DeveloperError('corner and oppositeCorner are required.');
}
if (!defined(result)) {
result = new BoundingSphere();
}
var center = result.center;
Cartesian3.add(corner, oppositeCorner, center);
Cartesian3.multiplyByScalar(center, 0.5, center);
result.radius = Cartesian3.distance(center, oppositeCorner);
return result;
};
/**
* Creates a bounding sphere encompassing an ellipsoid.
*
* @param {Ellipsoid} ellipsoid The ellipsoid around which to create a bounding sphere.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*
* @example
* var boundingSphere = Cesium.BoundingSphere.fromEllipsoid(ellipsoid);
*/
BoundingSphere.fromEllipsoid = function(ellipsoid, result) {
if (!defined(ellipsoid)) {
throw new DeveloperError('ellipsoid is required.');
}
if (!defined(result)) {
result = new BoundingSphere();
}
Cartesian3.clone(Cartesian3.ZERO, result.center);
result.radius = ellipsoid.maximumRadius;
return result;
};
var fromBoundingSpheresScratch = new Cartesian3();
/**
* Computes a tight-fitting bounding sphere enclosing the provided array of bounding spheres.
*
* @param {BoundingSphere[]} boundingSpheres The array of bounding spheres.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.fromBoundingSpheres = function(boundingSpheres, result) {
if (!defined(result)) {
result = new BoundingSphere();
}
if (!defined(boundingSpheres) || boundingSpheres.length === 0) {
result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
result.radius = 0.0;
return result;
}
var length = boundingSpheres.length;
if (length === 1) {
return BoundingSphere.clone(boundingSpheres[0], result);
}
if (length === 2) {
return BoundingSphere.union(boundingSpheres[0], boundingSpheres[1], result);
}
var positions = [];
for (var i = 0; i < length; i++) {
positions.push(boundingSpheres[i].center);
}
result = BoundingSphere.fromPoints(positions, result);
var center = result.center;
var radius = result.radius;
for (i = 0; i < length; i++) {
var tmp = boundingSpheres[i];
radius = Math.max(radius, Cartesian3.distance(center, tmp.center, fromBoundingSpheresScratch) + tmp.radius);
}
result.radius = radius;
return result;
};
var fromOrientedBoundingBoxScratchU = new Cartesian3();
var fromOrientedBoundingBoxScratchV = new Cartesian3();
var fromOrientedBoundingBoxScratchW = new Cartesian3();
/**
* Computes a tight-fitting bounding sphere enclosing the provided oriented bounding box.
*
* @param {OrientedBoundingBox} orientedBoundingBox The oriented bounding box.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.fromOrientedBoundingBox = function(orientedBoundingBox, result) {
if (!defined(result)) {
result = new BoundingSphere();
}
var halfAxes = orientedBoundingBox.halfAxes;
var u = Matrix3.getColumn(halfAxes, 0, fromOrientedBoundingBoxScratchU);
var v = Matrix3.getColumn(halfAxes, 1, fromOrientedBoundingBoxScratchV);
var w = Matrix3.getColumn(halfAxes, 2, fromOrientedBoundingBoxScratchW);
var uHalf = Cartesian3.magnitude(u);
var vHalf = Cartesian3.magnitude(v);
var wHalf = Cartesian3.magnitude(w);
result.center = Cartesian3.clone(orientedBoundingBox.center, result.center);
result.radius = Math.max(uHalf, vHalf, wHalf);
return result;
};
/**
* Duplicates a BoundingSphere instance.
*
* @param {BoundingSphere} sphere The bounding sphere to duplicate.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided. (Returns undefined if sphere is undefined)
*/
BoundingSphere.clone = function(sphere, result) {
if (!defined(sphere)) {
return undefined;
}
if (!defined(result)) {
return new BoundingSphere(sphere.center, sphere.radius);
}
result.center = Cartesian3.clone(sphere.center, result.center);
result.radius = sphere.radius;
return result;
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
BoundingSphere.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {BoundingSphere} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
BoundingSphere.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var center = value.center;
array[startingIndex++] = center.x;
array[startingIndex++] = center.y;
array[startingIndex++] = center.z;
array[startingIndex] = value.radius;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {BoundingSphere} [result] The object into which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
*/
BoundingSphere.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new BoundingSphere();
}
var center = result.center;
center.x = array[startingIndex++];
center.y = array[startingIndex++];
center.z = array[startingIndex++];
result.radius = array[startingIndex];
return result;
};
var unionScratch = new Cartesian3();
var unionScratchCenter = new Cartesian3();
/**
* Computes a bounding sphere that contains both the left and right bounding spheres.
*
* @param {BoundingSphere} left A sphere to enclose in a bounding sphere.
* @param {BoundingSphere} right A sphere to enclose in a bounding sphere.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.union = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
if (!defined(result)) {
result = new BoundingSphere();
}
var leftCenter = left.center;
var leftRadius = left.radius;
var rightCenter = right.center;
var rightRadius = right.radius;
var toRightCenter = Cartesian3.subtract(rightCenter, leftCenter, unionScratch);
var centerSeparation = Cartesian3.magnitude(toRightCenter);
if (leftRadius >= (centerSeparation + rightRadius)) {
// Left sphere wins.
left.clone(result);
return result;
}
if (rightRadius >= (centerSeparation + leftRadius)) {
// Right sphere wins.
right.clone(result);
return result;
}
// There are two tangent points, one on far side of each sphere.
var halfDistanceBetweenTangentPoints = (leftRadius + centerSeparation + rightRadius) * 0.5;
// Compute the center point halfway between the two tangent points.
var center = Cartesian3.multiplyByScalar(toRightCenter,
(-leftRadius + halfDistanceBetweenTangentPoints) / centerSeparation, unionScratchCenter);
Cartesian3.add(center, leftCenter, center);
Cartesian3.clone(center, result.center);
result.radius = halfDistanceBetweenTangentPoints;
return result;
};
var expandScratch = new Cartesian3();
/**
* Computes a bounding sphere by enlarging the provided sphere to contain the provided point.
*
* @param {BoundingSphere} sphere A sphere to expand.
* @param {Cartesian3} point A point to enclose in a bounding sphere.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.expand = function(sphere, point, result) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(point)) {
throw new DeveloperError('point is required.');
}
result = BoundingSphere.clone(sphere, result);
var radius = Cartesian3.magnitude(Cartesian3.subtract(point, result.center, expandScratch));
if (radius > result.radius) {
result.radius = radius;
}
return result;
};
/**
* Determines which side of a plane a sphere is located.
*
* @param {BoundingSphere} sphere The bounding sphere to test.
* @param {Plane} plane The plane to test against.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is
* on the opposite side, and {@link Intersect.INTERSECTING} if the sphere
* intersects the plane.
*/
BoundingSphere.intersectPlane = function(sphere, plane) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
var center = sphere.center;
var radius = sphere.radius;
var normal = plane.normal;
var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance;
if (distanceToPlane < -radius) {
// The center point is negative side of the plane normal
return Intersect.OUTSIDE;
} else if (distanceToPlane < radius) {
// The center point is positive side of the plane, but radius extends beyond it; partial overlap
return Intersect.INTERSECTING;
}
return Intersect.INSIDE;
};
/**
* Applies a 4x4 affine transformation matrix to a bounding sphere.
*
* @param {BoundingSphere} sphere The bounding sphere to apply the transformation to.
* @param {Matrix4} transform The transformation matrix to apply to the bounding sphere.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.transform = function(sphere, transform, result) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(transform)) {
throw new DeveloperError('transform is required.');
}
if (!defined(result)) {
result = new BoundingSphere();
}
result.center = Matrix4.multiplyByPoint(transform, sphere.center, result.center);
result.radius = Matrix4.getMaximumScale(transform) * sphere.radius;
return result;
};
var distanceSquaredToScratch = new Cartesian3();
/**
* Computes the estimated distance squared from the closest point on a bounding sphere to a point.
*
* @param {BoundingSphere} sphere The sphere.
* @param {Cartesian3} cartesian The point
* @returns {Number} The estimated distance squared from the bounding sphere to the point.
*
* @example
* // Sort bounding spheres from back to front
* spheres.sort(function(a, b) {
* return Cesium.BoundingSphere.distanceSquaredTo(b, camera.positionWC) - Cesium.BoundingSphere.distanceSquaredTo(a, camera.positionWC);
* });
*/
BoundingSphere.distanceSquaredTo = function(sphere, cartesian) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
var diff = Cartesian3.subtract(sphere.center, cartesian, distanceSquaredToScratch);
return Cartesian3.magnitudeSquared(diff) - sphere.radius * sphere.radius;
};
/**
* Applies a 4x4 affine transformation matrix to a bounding sphere where there is no scale
* The transformation matrix is not verified to have a uniform scale of 1.
* This method is faster than computing the general bounding sphere transform using {@link BoundingSphere.transform}.
*
* @param {BoundingSphere} sphere The bounding sphere to apply the transformation to.
* @param {Matrix4} transform The transformation matrix to apply to the bounding sphere.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*
* @example
* var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid);
* var boundingSphere = new Cesium.BoundingSphere();
* var newBoundingSphere = Cesium.BoundingSphere.transformWithoutScale(boundingSphere, modelMatrix);
*/
BoundingSphere.transformWithoutScale = function(sphere, transform, result) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(transform)) {
throw new DeveloperError('transform is required.');
}
if (!defined(result)) {
result = new BoundingSphere();
}
result.center = Matrix4.multiplyByPoint(transform, sphere.center, result.center);
result.radius = sphere.radius;
return result;
};
var scratchCartesian3 = new Cartesian3();
/**
* The distances calculated by the vector from the center of the bounding sphere to position projected onto direction
* plus/minus the radius of the bounding sphere.
*
* If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
* closest and farthest planes from position that intersect the bounding sphere.
*
* @param {BoundingSphere} sphere The bounding sphere to calculate the distance to.
* @param {Cartesian3} position The position to calculate the distance from.
* @param {Cartesian3} direction The direction from position.
* @param {Interval} [result] A Interval to store the nearest and farthest distances.
* @returns {Interval} The nearest and farthest distances on the bounding sphere from position in direction.
*/
BoundingSphere.computePlaneDistances = function(sphere, position, direction, result) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
if (!defined(direction)) {
throw new DeveloperError('direction is required.');
}
if (!defined(result)) {
result = new Interval();
}
var toCenter = Cartesian3.subtract(sphere.center, position, scratchCartesian3);
var mag = Cartesian3.dot(direction, toCenter);
result.start = mag - sphere.radius;
result.stop = mag + sphere.radius;
return result;
};
var projectTo2DNormalScratch = new Cartesian3();
var projectTo2DEastScratch = new Cartesian3();
var projectTo2DNorthScratch = new Cartesian3();
var projectTo2DWestScratch = new Cartesian3();
var projectTo2DSouthScratch = new Cartesian3();
var projectTo2DCartographicScratch = new Cartographic();
var projectTo2DPositionsScratch = new Array(8);
for (var n = 0; n < 8; ++n) {
projectTo2DPositionsScratch[n] = new Cartesian3();
}
var projectTo2DProjection = new GeographicProjection();
/**
* Creates a bounding sphere in 2D from a bounding sphere in 3D world coordinates.
*
* @param {BoundingSphere} sphere The bounding sphere to transform to 2D.
* @param {Object} [projection=GeographicProjection] The projection to 2D.
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.projectTo2D = function(sphere, projection, result) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
projection = defaultValue(projection, projectTo2DProjection);
var ellipsoid = projection.ellipsoid;
var center = sphere.center;
var radius = sphere.radius;
var normal = ellipsoid.geodeticSurfaceNormal(center, projectTo2DNormalScratch);
var east = Cartesian3.cross(Cartesian3.UNIT_Z, normal, projectTo2DEastScratch);
Cartesian3.normalize(east, east);
var north = Cartesian3.cross(normal, east, projectTo2DNorthScratch);
Cartesian3.normalize(north, north);
Cartesian3.multiplyByScalar(normal, radius, normal);
Cartesian3.multiplyByScalar(north, radius, north);
Cartesian3.multiplyByScalar(east, radius, east);
var south = Cartesian3.negate(north, projectTo2DSouthScratch);
var west = Cartesian3.negate(east, projectTo2DWestScratch);
var positions = projectTo2DPositionsScratch;
// top NE corner
var corner = positions[0];
Cartesian3.add(normal, north, corner);
Cartesian3.add(corner, east, corner);
// top NW corner
corner = positions[1];
Cartesian3.add(normal, north, corner);
Cartesian3.add(corner, west, corner);
// top SW corner
corner = positions[2];
Cartesian3.add(normal, south, corner);
Cartesian3.add(corner, west, corner);
// top SE corner
corner = positions[3];
Cartesian3.add(normal, south, corner);
Cartesian3.add(corner, east, corner);
Cartesian3.negate(normal, normal);
// bottom NE corner
corner = positions[4];
Cartesian3.add(normal, north, corner);
Cartesian3.add(corner, east, corner);
// bottom NW corner
corner = positions[5];
Cartesian3.add(normal, north, corner);
Cartesian3.add(corner, west, corner);
// bottom SW corner
corner = positions[6];
Cartesian3.add(normal, south, corner);
Cartesian3.add(corner, west, corner);
// bottom SE corner
corner = positions[7];
Cartesian3.add(normal, south, corner);
Cartesian3.add(corner, east, corner);
var length = positions.length;
for (var i = 0; i < length; ++i) {
var position = positions[i];
Cartesian3.add(center, position, position);
var cartographic = ellipsoid.cartesianToCartographic(position, projectTo2DCartographicScratch);
projection.project(cartographic, position);
}
result = BoundingSphere.fromPoints(positions, result);
// swizzle center components
center = result.center;
var x = center.x;
var y = center.y;
var z = center.z;
center.x = z;
center.y = x;
center.z = y;
return result;
};
/**
* Determines whether or not a sphere is hidden from view by the occluder.
*
* @param {BoundingSphere} sphere The bounding sphere surrounding the occludee object.
* @param {Occluder} occluder The occluder.
* @returns {Boolean} true
if the sphere is not visible; otherwise false
.
*/
BoundingSphere.isOccluded = function(sphere, occluder) {
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
if (!defined(occluder)) {
throw new DeveloperError('occluder is required.');
}
return !occluder.isBoundingSphereVisible(sphere);
};
/**
* Compares the provided BoundingSphere componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {BoundingSphere} [left] The first BoundingSphere.
* @param {BoundingSphere} [right] The second BoundingSphere.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
BoundingSphere.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
Cartesian3.equals(left.center, right.center) &&
left.radius === right.radius);
};
/**
* Determines which side of a plane the sphere is located.
*
* @param {Plane} plane The plane to test against.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is
* on the opposite side, and {@link Intersect.INTERSECTING} if the sphere
* intersects the plane.
*/
BoundingSphere.prototype.intersectPlane = function(plane) {
return BoundingSphere.intersectPlane(this, plane);
};
/**
* Computes the estimated distance squared from the closest point on a bounding sphere to a point.
*
* @param {Cartesian3} cartesian The point
* @returns {Number} The estimated distance squared from the bounding sphere to the point.
*
* @example
* // Sort bounding spheres from back to front
* spheres.sort(function(a, b) {
* return b.distanceSquaredTo(camera.positionWC) - a.distanceSquaredTo(camera.positionWC);
* });
*/
BoundingSphere.prototype.distanceSquaredTo = function(cartesian) {
return BoundingSphere.distanceSquaredTo(this, cartesian);
};
/**
* The distances calculated by the vector from the center of the bounding sphere to position projected onto direction
* plus/minus the radius of the bounding sphere.
*
* If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
* closest and farthest planes from position that intersect the bounding sphere.
*
* @param {Cartesian3} position The position to calculate the distance from.
* @param {Cartesian3} direction The direction from position.
* @param {Interval} [result] A Interval to store the nearest and farthest distances.
* @returns {Interval} The nearest and farthest distances on the bounding sphere from position in direction.
*/
BoundingSphere.prototype.computePlaneDistances = function(position, direction, result) {
return BoundingSphere.computePlaneDistances(this, position, direction, result);
};
/**
* Determines whether or not a sphere is hidden from view by the occluder.
*
* @param {Occluder} occluder The occluder.
* @returns {Boolean} true
if the sphere is not visible; otherwise false
.
*/
BoundingSphere.prototype.isOccluded = function(occluder) {
return BoundingSphere.isOccluded(this, occluder);
};
/**
* Compares this BoundingSphere against the provided BoundingSphere componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {BoundingSphere} [right] The right hand side BoundingSphere.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
BoundingSphere.prototype.equals = function(right) {
return BoundingSphere.equals(this, right);
};
/**
* Duplicates this BoundingSphere instance.
*
* @param {BoundingSphere} [result] The object onto which to store the result.
* @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
*/
BoundingSphere.prototype.clone = function(result) {
return BoundingSphere.clone(this, result);
};
return BoundingSphere;
});
/*global define*/
define('Core/EllipsoidalOccluder',[
'./BoundingSphere',
'./Cartesian3',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Rectangle'
], function(
BoundingSphere,
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Rectangle) {
'use strict';
/**
* Determine whether or not other objects are visible or hidden behind the visible horizon defined by
* an {@link Ellipsoid} and a camera position. The ellipsoid is assumed to be located at the
* origin of the coordinate system. This class uses the algorithm described in the
* {@link http://cesiumjs.org/2013/04/25/Horizon-culling/|Horizon Culling} blog post.
*
* @alias EllipsoidalOccluder
*
* @param {Ellipsoid} ellipsoid The ellipsoid to use as an occluder.
* @param {Cartesian3} [cameraPosition] The coordinate of the viewer/camera. If this parameter is not
* specified, {@link EllipsoidalOccluder#cameraPosition} must be called before
* testing visibility.
*
* @constructor
*
* @example
* // Construct an ellipsoidal occluder with radii 1.0, 1.1, and 0.9.
* var cameraPosition = new Cesium.Cartesian3(5.0, 6.0, 7.0);
* var occluderEllipsoid = new Cesium.Ellipsoid(1.0, 1.1, 0.9);
* var occluder = new Cesium.EllipsoidalOccluder(occluderEllipsoid, cameraPosition);
*
* @private
*/
function EllipsoidalOccluder(ellipsoid, cameraPosition) {
if (!defined(ellipsoid)) {
throw new DeveloperError('ellipsoid is required.');
}
this._ellipsoid = ellipsoid;
this._cameraPosition = new Cartesian3();
this._cameraPositionInScaledSpace = new Cartesian3();
this._distanceToLimbInScaledSpaceSquared = 0.0;
// cameraPosition fills in the above values
if (defined(cameraPosition)) {
this.cameraPosition = cameraPosition;
}
}
defineProperties(EllipsoidalOccluder.prototype, {
/**
* Gets the occluding ellipsoid.
* @memberof EllipsoidalOccluder.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get: function() {
return this._ellipsoid;
}
},
/**
* Gets or sets the position of the camera.
* @memberof EllipsoidalOccluder.prototype
* @type {Cartesian3}
*/
cameraPosition : {
get : function() {
return this._cameraPosition;
},
set : function(cameraPosition) {
// See http://cesiumjs.org/2013/04/25/Horizon-culling/
var ellipsoid = this._ellipsoid;
var cv = ellipsoid.transformPositionToScaledSpace(cameraPosition, this._cameraPositionInScaledSpace);
var vhMagnitudeSquared = Cartesian3.magnitudeSquared(cv) - 1.0;
Cartesian3.clone(cameraPosition, this._cameraPosition);
this._cameraPositionInScaledSpace = cv;
this._distanceToLimbInScaledSpaceSquared = vhMagnitudeSquared;
}
}
});
var scratchCartesian = new Cartesian3();
/**
* Determines whether or not a point, the occludee
, is hidden from view by the occluder.
*
* @param {Cartesian3} occludee The point to test for visibility.
* @returns {Boolean} true
if the occludee is visible; otherwise false
.
*
* @example
* var cameraPosition = new Cesium.Cartesian3(0, 0, 2.5);
* var ellipsoid = new Cesium.Ellipsoid(1.0, 1.1, 0.9);
* var occluder = new Cesium.EllipsoidalOccluder(ellipsoid, cameraPosition);
* var point = new Cesium.Cartesian3(0, -3, -3);
* occluder.isPointVisible(point); //returns true
*/
EllipsoidalOccluder.prototype.isPointVisible = function(occludee) {
var ellipsoid = this._ellipsoid;
var occludeeScaledSpacePosition = ellipsoid.transformPositionToScaledSpace(occludee, scratchCartesian);
return this.isScaledSpacePointVisible(occludeeScaledSpacePosition);
};
/**
* Determines whether or not a point expressed in the ellipsoid scaled space, is hidden from view by the
* occluder. To transform a Cartesian X, Y, Z position in the coordinate system aligned with the ellipsoid
* into the scaled space, call {@link Ellipsoid#transformPositionToScaledSpace}.
*
* @param {Cartesian3} occludeeScaledSpacePosition The point to test for visibility, represented in the scaled space.
* @returns {Boolean} true
if the occludee is visible; otherwise false
.
*
* @example
* var cameraPosition = new Cesium.Cartesian3(0, 0, 2.5);
* var ellipsoid = new Cesium.Ellipsoid(1.0, 1.1, 0.9);
* var occluder = new Cesium.EllipsoidalOccluder(ellipsoid, cameraPosition);
* var point = new Cesium.Cartesian3(0, -3, -3);
* var scaledSpacePoint = ellipsoid.transformPositionToScaledSpace(point);
* occluder.isScaledSpacePointVisible(scaledSpacePoint); //returns true
*/
EllipsoidalOccluder.prototype.isScaledSpacePointVisible = function(occludeeScaledSpacePosition) {
// See http://cesiumjs.org/2013/04/25/Horizon-culling/
var cv = this._cameraPositionInScaledSpace;
var vhMagnitudeSquared = this._distanceToLimbInScaledSpaceSquared;
var vt = Cartesian3.subtract(occludeeScaledSpacePosition, cv, scratchCartesian);
var vtDotVc = -Cartesian3.dot(vt, cv);
// If vhMagnitudeSquared < 0 then we are below the surface of the ellipsoid and
// in this case, set the culling plane to be on V.
var isOccluded = vhMagnitudeSquared < 0 ? vtDotVc > 0 : (vtDotVc > vhMagnitudeSquared &&
vtDotVc * vtDotVc / Cartesian3.magnitudeSquared(vt) > vhMagnitudeSquared);
return !isOccluded;
};
/**
* Computes a point that can be used for horizon culling from a list of positions. If the point is below
* the horizon, all of the positions are guaranteed to be below the horizon as well. The returned point
* is expressed in the ellipsoid-scaled space and is suitable for use with
* {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
*
* @param {Cartesian3} directionToPoint The direction that the computed point will lie along.
* A reasonable direction to use is the direction from the center of the ellipsoid to
* the center of the bounding sphere computed from the positions. The direction need not
* be normalized.
* @param {Cartesian3[]} positions The positions from which to compute the horizon culling point. The positions
* must be expressed in a reference frame centered at the ellipsoid and aligned with the
* ellipsoid's axes.
* @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
* @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPoint = function(directionToPoint, positions, result) {
if (!defined(directionToPoint)) {
throw new DeveloperError('directionToPoint is required');
}
if (!defined(positions)) {
throw new DeveloperError('positions is required');
}
if (!defined(result)) {
result = new Cartesian3();
}
var ellipsoid = this._ellipsoid;
var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
var resultMagnitude = 0.0;
for (var i = 0, len = positions.length; i < len; ++i) {
var position = positions[i];
var candidateMagnitude = computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint);
resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
}
return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
};
var positionScratch = new Cartesian3();
/**
* Computes a point that can be used for horizon culling from a list of positions. If the point is below
* the horizon, all of the positions are guaranteed to be below the horizon as well. The returned point
* is expressed in the ellipsoid-scaled space and is suitable for use with
* {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
*
* @param {Cartesian3} directionToPoint The direction that the computed point will lie along.
* A reasonable direction to use is the direction from the center of the ellipsoid to
* the center of the bounding sphere computed from the positions. The direction need not
* be normalized.
* @param {Number[]} vertices The vertices from which to compute the horizon culling point. The positions
* must be expressed in a reference frame centered at the ellipsoid and aligned with the
* ellipsoid's axes.
* @param {Number} [stride=3]
* @param {Cartesian3} [center=Cartesian3.ZERO]
* @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
* @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointFromVertices = function(directionToPoint, vertices, stride, center, result) {
if (!defined(directionToPoint)) {
throw new DeveloperError('directionToPoint is required');
}
if (!defined(vertices)) {
throw new DeveloperError('vertices is required');
}
if (!defined(stride)) {
throw new DeveloperError('stride is required');
}
if (!defined(result)) {
result = new Cartesian3();
}
center = defaultValue(center, Cartesian3.ZERO);
var ellipsoid = this._ellipsoid;
var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
var resultMagnitude = 0.0;
for (var i = 0, len = vertices.length; i < len; i += stride) {
positionScratch.x = vertices[i] + center.x;
positionScratch.y = vertices[i + 1] + center.y;
positionScratch.z = vertices[i + 2] + center.z;
var candidateMagnitude = computeMagnitude(ellipsoid, positionScratch, scaledSpaceDirectionToPoint);
resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
}
return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
};
var subsampleScratch = [];
/**
* Computes a point that can be used for horizon culling of an rectangle. If the point is below
* the horizon, the ellipsoid-conforming rectangle is guaranteed to be below the horizon as well.
* The returned point is expressed in the ellipsoid-scaled space and is suitable for use with
* {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
*
* @param {Rectangle} rectangle The rectangle for which to compute the horizon culling point.
* @param {Ellipsoid} ellipsoid The ellipsoid on which the rectangle is defined. This may be different from
* the ellipsoid used by this instance for occlusion testing.
* @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
* @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
*/
EllipsoidalOccluder.prototype.computeHorizonCullingPointFromRectangle = function(rectangle, ellipsoid, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
var positions = Rectangle.subsample(rectangle, ellipsoid, 0.0, subsampleScratch);
var bs = BoundingSphere.fromPoints(positions);
// If the bounding sphere center is too close to the center of the occluder, it doesn't make
// sense to try to horizon cull it.
if (Cartesian3.magnitude(bs.center) < 0.1 * ellipsoid.minimumRadius) {
return undefined;
}
return this.computeHorizonCullingPoint(bs.center, positions, result);
};
var scaledSpaceScratch = new Cartesian3();
var directionScratch = new Cartesian3();
function computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint) {
var scaledSpacePosition = ellipsoid.transformPositionToScaledSpace(position, scaledSpaceScratch);
var magnitudeSquared = Cartesian3.magnitudeSquared(scaledSpacePosition);
var magnitude = Math.sqrt(magnitudeSquared);
var direction = Cartesian3.divideByScalar(scaledSpacePosition, magnitude, directionScratch);
// For the purpose of this computation, points below the ellipsoid are consider to be on it instead.
magnitudeSquared = Math.max(1.0, magnitudeSquared);
magnitude = Math.max(1.0, magnitude);
var cosAlpha = Cartesian3.dot(direction, scaledSpaceDirectionToPoint);
var sinAlpha = Cartesian3.magnitude(Cartesian3.cross(direction, scaledSpaceDirectionToPoint, direction));
var cosBeta = 1.0 / magnitude;
var sinBeta = Math.sqrt(magnitudeSquared - 1.0) * cosBeta;
return 1.0 / (cosAlpha * cosBeta - sinAlpha * sinBeta);
}
function magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result) {
// The horizon culling point is undefined if there were no positions from which to compute it,
// the directionToPoint is pointing opposite all of the positions, or if we computed NaN or infinity.
if (resultMagnitude <= 0.0 || resultMagnitude === 1.0 / 0.0 || resultMagnitude !== resultMagnitude) {
return undefined;
}
return Cartesian3.multiplyByScalar(scaledSpaceDirectionToPoint, resultMagnitude, result);
}
var directionToPointScratch = new Cartesian3();
function computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint) {
if (Cartesian3.equals(directionToPoint, Cartesian3.ZERO)) {
return directionToPoint;
}
ellipsoid.transformPositionToScaledSpace(directionToPoint, directionToPointScratch);
return Cartesian3.normalize(directionToPointScratch, directionToPointScratch);
}
return EllipsoidalOccluder;
});
/*global define*/
define('Core/QuadraticRealPolynomial',[
'./DeveloperError',
'./Math'
], function(
DeveloperError,
CesiumMath) {
'use strict';
/**
* Defines functions for 2nd order polynomial functions of one variable with only real coefficients.
*
* @exports QuadraticRealPolynomial
*/
var QuadraticRealPolynomial = {};
/**
* Provides the discriminant of the quadratic equation from the supplied coefficients.
*
* @param {Number} a The coefficient of the 2nd order monomial.
* @param {Number} b The coefficient of the 1st order monomial.
* @param {Number} c The coefficient of the 0th order monomial.
* @returns {Number} The value of the discriminant.
*/
QuadraticRealPolynomial.computeDiscriminant = function(a, b, c) {
if (typeof a !== 'number') {
throw new DeveloperError('a is a required number.');
}
if (typeof b !== 'number') {
throw new DeveloperError('b is a required number.');
}
if (typeof c !== 'number') {
throw new DeveloperError('c is a required number.');
}
var discriminant = b * b - 4.0 * a * c;
return discriminant;
};
function addWithCancellationCheck(left, right, tolerance) {
var difference = left + right;
if ((CesiumMath.sign(left) !== CesiumMath.sign(right)) &&
Math.abs(difference / Math.max(Math.abs(left), Math.abs(right))) < tolerance) {
return 0.0;
}
return difference;
}
/**
* Provides the real valued roots of the quadratic polynomial with the provided coefficients.
*
* @param {Number} a The coefficient of the 2nd order monomial.
* @param {Number} b The coefficient of the 1st order monomial.
* @param {Number} c The coefficient of the 0th order monomial.
* @returns {Number[]} The real valued roots.
*/
QuadraticRealPolynomial.computeRealRoots = function(a, b, c) {
if (typeof a !== 'number') {
throw new DeveloperError('a is a required number.');
}
if (typeof b !== 'number') {
throw new DeveloperError('b is a required number.');
}
if (typeof c !== 'number') {
throw new DeveloperError('c is a required number.');
}
var ratio;
if (a === 0.0) {
if (b === 0.0) {
// Constant function: c = 0.
return [];
}
// Linear function: b * x + c = 0.
return [-c / b];
} else if (b === 0.0) {
if (c === 0.0) {
// 2nd order monomial: a * x^2 = 0.
return [0.0, 0.0];
}
var cMagnitude = Math.abs(c);
var aMagnitude = Math.abs(a);
if ((cMagnitude < aMagnitude) && (cMagnitude / aMagnitude < CesiumMath.EPSILON14)) { // c ~= 0.0.
// 2nd order monomial: a * x^2 = 0.
return [0.0, 0.0];
} else if ((cMagnitude > aMagnitude) && (aMagnitude / cMagnitude < CesiumMath.EPSILON14)) { // a ~= 0.0.
// Constant function: c = 0.
return [];
}
// a * x^2 + c = 0
ratio = -c / a;
if (ratio < 0.0) {
// Both roots are complex.
return [];
}
// Both roots are real.
var root = Math.sqrt(ratio);
return [-root, root];
} else if (c === 0.0) {
// a * x^2 + b * x = 0
ratio = -b / a;
if (ratio < 0.0) {
return [ratio, 0.0];
}
return [0.0, ratio];
}
// a * x^2 + b * x + c = 0
var b2 = b * b;
var four_ac = 4.0 * a * c;
var radicand = addWithCancellationCheck(b2, -four_ac, CesiumMath.EPSILON14);
if (radicand < 0.0) {
// Both roots are complex.
return [];
}
var q = -0.5 * addWithCancellationCheck(b, CesiumMath.sign(b) * Math.sqrt(radicand), CesiumMath.EPSILON14);
if (b > 0.0) {
return [q / a, c / q];
}
return [c / q, q / a];
};
return QuadraticRealPolynomial;
});
/*global define*/
define('Core/CubicRealPolynomial',[
'./DeveloperError',
'./QuadraticRealPolynomial'
], function(
DeveloperError,
QuadraticRealPolynomial) {
'use strict';
/**
* Defines functions for 3rd order polynomial functions of one variable with only real coefficients.
*
* @exports CubicRealPolynomial
*/
var CubicRealPolynomial = {};
/**
* Provides the discriminant of the cubic equation from the supplied coefficients.
*
* @param {Number} a The coefficient of the 3rd order monomial.
* @param {Number} b The coefficient of the 2nd order monomial.
* @param {Number} c The coefficient of the 1st order monomial.
* @param {Number} d The coefficient of the 0th order monomial.
* @returns {Number} The value of the discriminant.
*/
CubicRealPolynomial.computeDiscriminant = function(a, b, c, d) {
if (typeof a !== 'number') {
throw new DeveloperError('a is a required number.');
}
if (typeof b !== 'number') {
throw new DeveloperError('b is a required number.');
}
if (typeof c !== 'number') {
throw new DeveloperError('c is a required number.');
}
if (typeof d !== 'number') {
throw new DeveloperError('d is a required number.');
}
var a2 = a * a;
var b2 = b * b;
var c2 = c * c;
var d2 = d * d;
var discriminant = 18.0 * a * b * c * d + b2 * c2 - 27.0 * a2 * d2 - 4.0 * (a * c2 * c + b2 * b * d);
return discriminant;
};
function computeRealRoots(a, b, c, d) {
var A = a;
var B = b / 3.0;
var C = c / 3.0;
var D = d;
var AC = A * C;
var BD = B * D;
var B2 = B * B;
var C2 = C * C;
var delta1 = A * C - B2;
var delta2 = A * D - B * C;
var delta3 = B * D - C2;
var discriminant = 4.0 * delta1 * delta3 - delta2 * delta2;
var temp;
var temp1;
if (discriminant < 0.0) {
var ABar;
var CBar;
var DBar;
if (B2 * BD >= AC * C2) {
ABar = A;
CBar = delta1;
DBar = -2.0 * B * delta1 + A * delta2;
} else {
ABar = D;
CBar = delta3;
DBar = -D * delta2 + 2.0 * C * delta3;
}
var s = (DBar < 0.0) ? -1.0 : 1.0; // This is not Math.Sign()!
var temp0 = -s * Math.abs(ABar) * Math.sqrt(-discriminant);
temp1 = -DBar + temp0;
var x = temp1 / 2.0;
var p = x < 0.0 ? -Math.pow(-x, 1.0 / 3.0) : Math.pow(x, 1.0 / 3.0);
var q = (temp1 === temp0) ? -p : -CBar / p;
temp = (CBar <= 0.0) ? p + q : -DBar / (p * p + q * q + CBar);
if (B2 * BD >= AC * C2) {
return [(temp - B) / A];
}
return [-D / (temp + C)];
}
var CBarA = delta1;
var DBarA = -2.0 * B * delta1 + A * delta2;
var CBarD = delta3;
var DBarD = -D * delta2 + 2.0 * C * delta3;
var squareRootOfDiscriminant = Math.sqrt(discriminant);
var halfSquareRootOf3 = Math.sqrt(3.0) / 2.0;
var theta = Math.abs(Math.atan2(A * squareRootOfDiscriminant, -DBarA) / 3.0);
temp = 2.0 * Math.sqrt(-CBarA);
var cosine = Math.cos(theta);
temp1 = temp * cosine;
var temp3 = temp * (-cosine / 2.0 - halfSquareRootOf3 * Math.sin(theta));
var numeratorLarge = (temp1 + temp3 > 2.0 * B) ? temp1 - B : temp3 - B;
var denominatorLarge = A;
var root1 = numeratorLarge / denominatorLarge;
theta = Math.abs(Math.atan2(D * squareRootOfDiscriminant, -DBarD) / 3.0);
temp = 2.0 * Math.sqrt(-CBarD);
cosine = Math.cos(theta);
temp1 = temp * cosine;
temp3 = temp * (-cosine / 2.0 - halfSquareRootOf3 * Math.sin(theta));
var numeratorSmall = -D;
var denominatorSmall = (temp1 + temp3 < 2.0 * C) ? temp1 + C : temp3 + C;
var root3 = numeratorSmall / denominatorSmall;
var E = denominatorLarge * denominatorSmall;
var F = -numeratorLarge * denominatorSmall - denominatorLarge * numeratorSmall;
var G = numeratorLarge * numeratorSmall;
var root2 = (C * F - B * G) / (-B * F + C * E);
if (root1 <= root2) {
if (root1 <= root3) {
if (root2 <= root3) {
return [root1, root2, root3];
}
return [root1, root3, root2];
}
return [root3, root1, root2];
}
if (root1 <= root3) {
return [root2, root1, root3];
}
if (root2 <= root3) {
return [root2, root3, root1];
}
return [root3, root2, root1];
}
/**
* Provides the real valued roots of the cubic polynomial with the provided coefficients.
*
* @param {Number} a The coefficient of the 3rd order monomial.
* @param {Number} b The coefficient of the 2nd order monomial.
* @param {Number} c The coefficient of the 1st order monomial.
* @param {Number} d The coefficient of the 0th order monomial.
* @returns {Number[]} The real valued roots.
*/
CubicRealPolynomial.computeRealRoots = function(a, b, c, d) {
if (typeof a !== 'number') {
throw new DeveloperError('a is a required number.');
}
if (typeof b !== 'number') {
throw new DeveloperError('b is a required number.');
}
if (typeof c !== 'number') {
throw new DeveloperError('c is a required number.');
}
if (typeof d !== 'number') {
throw new DeveloperError('d is a required number.');
}
var roots;
var ratio;
if (a === 0.0) {
// Quadratic function: b * x^2 + c * x + d = 0.
return QuadraticRealPolynomial.computeRealRoots(b, c, d);
} else if (b === 0.0) {
if (c === 0.0) {
if (d === 0.0) {
// 3rd order monomial: a * x^3 = 0.
return [0.0, 0.0, 0.0];
}
// a * x^3 + d = 0
ratio = -d / a;
var root = (ratio < 0.0) ? -Math.pow(-ratio, 1.0 / 3.0) : Math.pow(ratio, 1.0 / 3.0);
return [root, root, root];
} else if (d === 0.0) {
// x * (a * x^2 + c) = 0.
roots = QuadraticRealPolynomial.computeRealRoots(a, 0, c);
// Return the roots in ascending order.
if (roots.Length === 0) {
return [0.0];
}
return [roots[0], 0.0, roots[1]];
}
// Deflated cubic polynomial: a * x^3 + c * x + d= 0.
return computeRealRoots(a, 0, c, d);
} else if (c === 0.0) {
if (d === 0.0) {
// x^2 * (a * x + b) = 0.
ratio = -b / a;
if (ratio < 0.0) {
return [ratio, 0.0, 0.0];
}
return [0.0, 0.0, ratio];
}
// a * x^3 + b * x^2 + d = 0.
return computeRealRoots(a, b, 0, d);
} else if (d === 0.0) {
// x * (a * x^2 + b * x + c) = 0
roots = QuadraticRealPolynomial.computeRealRoots(a, b, c);
// Return the roots in ascending order.
if (roots.length === 0) {
return [0.0];
} else if (roots[1] <= 0.0) {
return [roots[0], roots[1], 0.0];
} else if (roots[0] >= 0.0) {
return [0.0, roots[0], roots[1]];
}
return [roots[0], 0.0, roots[1]];
}
return computeRealRoots(a, b, c, d);
};
return CubicRealPolynomial;
});
/*global define*/
define('Core/QuarticRealPolynomial',[
'./CubicRealPolynomial',
'./DeveloperError',
'./Math',
'./QuadraticRealPolynomial'
], function(
CubicRealPolynomial,
DeveloperError,
CesiumMath,
QuadraticRealPolynomial) {
'use strict';
/**
* Defines functions for 4th order polynomial functions of one variable with only real coefficients.
*
* @exports QuarticRealPolynomial
*/
var QuarticRealPolynomial = {};
/**
* Provides the discriminant of the quartic equation from the supplied coefficients.
*
* @param {Number} a The coefficient of the 4th order monomial.
* @param {Number} b The coefficient of the 3rd order monomial.
* @param {Number} c The coefficient of the 2nd order monomial.
* @param {Number} d The coefficient of the 1st order monomial.
* @param {Number} e The coefficient of the 0th order monomial.
* @returns {Number} The value of the discriminant.
*/
QuarticRealPolynomial.computeDiscriminant = function(a, b, c, d, e) {
if (typeof a !== 'number') {
throw new DeveloperError('a is a required number.');
}
if (typeof b !== 'number') {
throw new DeveloperError('b is a required number.');
}
if (typeof c !== 'number') {
throw new DeveloperError('c is a required number.');
}
if (typeof d !== 'number') {
throw new DeveloperError('d is a required number.');
}
if (typeof e !== 'number') {
throw new DeveloperError('e is a required number.');
}
var a2 = a * a;
var a3 = a2 * a;
var b2 = b * b;
var b3 = b2 * b;
var c2 = c * c;
var c3 = c2 * c;
var d2 = d * d;
var d3 = d2 * d;
var e2 = e * e;
var e3 = e2 * e;
var discriminant = (b2 * c2 * d2 - 4.0 * b3 * d3 - 4.0 * a * c3 * d2 + 18 * a * b * c * d3 - 27.0 * a2 * d2 * d2 + 256.0 * a3 * e3) +
e * (18.0 * b3 * c * d - 4.0 * b2 * c3 + 16.0 * a * c2 * c2 - 80.0 * a * b * c2 * d - 6.0 * a * b2 * d2 + 144.0 * a2 * c * d2) +
e2 * (144.0 * a * b2 * c - 27.0 * b2 * b2 - 128.0 * a2 * c2 - 192.0 * a2 * b * d);
return discriminant;
};
function original(a3, a2, a1, a0) {
var a3Squared = a3 * a3;
var p = a2 - 3.0 * a3Squared / 8.0;
var q = a1 - a2 * a3 / 2.0 + a3Squared * a3 / 8.0;
var r = a0 - a1 * a3 / 4.0 + a2 * a3Squared / 16.0 - 3.0 * a3Squared * a3Squared / 256.0;
// Find the roots of the cubic equations: h^6 + 2 p h^4 + (p^2 - 4 r) h^2 - q^2 = 0.
var cubicRoots = CubicRealPolynomial.computeRealRoots(1.0, 2.0 * p, p * p - 4.0 * r, -q * q);
if (cubicRoots.length > 0) {
var temp = -a3 / 4.0;
// Use the largest positive root.
var hSquared = cubicRoots[cubicRoots.length - 1];
if (Math.abs(hSquared) < CesiumMath.EPSILON14) {
// y^4 + p y^2 + r = 0.
var roots = QuadraticRealPolynomial.computeRealRoots(1.0, p, r);
if (roots.length === 2) {
var root0 = roots[0];
var root1 = roots[1];
var y;
if (root0 >= 0.0 && root1 >= 0.0) {
var y0 = Math.sqrt(root0);
var y1 = Math.sqrt(root1);
return [temp - y1, temp - y0, temp + y0, temp + y1];
} else if (root0 >= 0.0 && root1 < 0.0) {
y = Math.sqrt(root0);
return [temp - y, temp + y];
} else if (root0 < 0.0 && root1 >= 0.0) {
y = Math.sqrt(root1);
return [temp - y, temp + y];
}
}
return [];
} else if (hSquared > 0.0) {
var h = Math.sqrt(hSquared);
var m = (p + hSquared - q / h) / 2.0;
var n = (p + hSquared + q / h) / 2.0;
// Now solve the two quadratic factors: (y^2 + h y + m)(y^2 - h y + n);
var roots1 = QuadraticRealPolynomial.computeRealRoots(1.0, h, m);
var roots2 = QuadraticRealPolynomial.computeRealRoots(1.0, -h, n);
if (roots1.length !== 0) {
roots1[0] += temp;
roots1[1] += temp;
if (roots2.length !== 0) {
roots2[0] += temp;
roots2[1] += temp;
if (roots1[1] <= roots2[0]) {
return [roots1[0], roots1[1], roots2[0], roots2[1]];
} else if (roots2[1] <= roots1[0]) {
return [roots2[0], roots2[1], roots1[0], roots1[1]];
} else if (roots1[0] >= roots2[0] && roots1[1] <= roots2[1]) {
return [roots2[0], roots1[0], roots1[1], roots2[1]];
} else if (roots2[0] >= roots1[0] && roots2[1] <= roots1[1]) {
return [roots1[0], roots2[0], roots2[1], roots1[1]];
} else if (roots1[0] > roots2[0] && roots1[0] < roots2[1]) {
return [roots2[0], roots1[0], roots2[1], roots1[1]];
}
return [roots1[0], roots2[0], roots1[1], roots2[1]];
}
return roots1;
}
if (roots2.length !== 0) {
roots2[0] += temp;
roots2[1] += temp;
return roots2;
}
return [];
}
}
return [];
}
function neumark(a3, a2, a1, a0) {
var a1Squared = a1 * a1;
var a2Squared = a2 * a2;
var a3Squared = a3 * a3;
var p = -2.0 * a2;
var q = a1 * a3 + a2Squared - 4.0 * a0;
var r = a3Squared * a0 - a1 * a2 * a3 + a1Squared;
var cubicRoots = CubicRealPolynomial.computeRealRoots(1.0, p, q, r);
if (cubicRoots.length > 0) {
// Use the most positive root
var y = cubicRoots[0];
var temp = (a2 - y);
var tempSquared = temp * temp;
var g1 = a3 / 2.0;
var h1 = temp / 2.0;
var m = tempSquared - 4.0 * a0;
var mError = tempSquared + 4.0 * Math.abs(a0);
var n = a3Squared - 4.0 * y;
var nError = a3Squared + 4.0 * Math.abs(y);
var g2;
var h2;
if (y < 0.0 || (m * nError < n * mError)) {
var squareRootOfN = Math.sqrt(n);
g2 = squareRootOfN / 2.0;
h2 = squareRootOfN === 0.0 ? 0.0 : (a3 * h1 - a1) / squareRootOfN;
} else {
var squareRootOfM = Math.sqrt(m);
g2 = squareRootOfM === 0.0 ? 0.0 : (a3 * h1 - a1) / squareRootOfM;
h2 = squareRootOfM / 2.0;
}
var G;
var g;
if (g1 === 0.0 && g2 === 0.0) {
G = 0.0;
g = 0.0;
} else if (CesiumMath.sign(g1) === CesiumMath.sign(g2)) {
G = g1 + g2;
g = y / G;
} else {
g = g1 - g2;
G = y / g;
}
var H;
var h;
if (h1 === 0.0 && h2 === 0.0) {
H = 0.0;
h = 0.0;
} else if (CesiumMath.sign(h1) === CesiumMath.sign(h2)) {
H = h1 + h2;
h = a0 / H;
} else {
h = h1 - h2;
H = a0 / h;
}
// Now solve the two quadratic factors: (y^2 + G y + H)(y^2 + g y + h);
var roots1 = QuadraticRealPolynomial.computeRealRoots(1.0, G, H);
var roots2 = QuadraticRealPolynomial.computeRealRoots(1.0, g, h);
if (roots1.length !== 0) {
if (roots2.length !== 0) {
if (roots1[1] <= roots2[0]) {
return [roots1[0], roots1[1], roots2[0], roots2[1]];
} else if (roots2[1] <= roots1[0]) {
return [roots2[0], roots2[1], roots1[0], roots1[1]];
} else if (roots1[0] >= roots2[0] && roots1[1] <= roots2[1]) {
return [roots2[0], roots1[0], roots1[1], roots2[1]];
} else if (roots2[0] >= roots1[0] && roots2[1] <= roots1[1]) {
return [roots1[0], roots2[0], roots2[1], roots1[1]];
} else if (roots1[0] > roots2[0] && roots1[0] < roots2[1]) {
return [roots2[0], roots1[0], roots2[1], roots1[1]];
} else {
return [roots1[0], roots2[0], roots1[1], roots2[1]];
}
}
return roots1;
}
if (roots2.length !== 0) {
return roots2;
}
}
return [];
}
/**
* Provides the real valued roots of the quartic polynomial with the provided coefficients.
*
* @param {Number} a The coefficient of the 4th order monomial.
* @param {Number} b The coefficient of the 3rd order monomial.
* @param {Number} c The coefficient of the 2nd order monomial.
* @param {Number} d The coefficient of the 1st order monomial.
* @param {Number} e The coefficient of the 0th order monomial.
* @returns {Number[]} The real valued roots.
*/
QuarticRealPolynomial.computeRealRoots = function(a, b, c, d, e) {
if (typeof a !== 'number') {
throw new DeveloperError('a is a required number.');
}
if (typeof b !== 'number') {
throw new DeveloperError('b is a required number.');
}
if (typeof c !== 'number') {
throw new DeveloperError('c is a required number.');
}
if (typeof d !== 'number') {
throw new DeveloperError('d is a required number.');
}
if (typeof e !== 'number') {
throw new DeveloperError('e is a required number.');
}
if (Math.abs(a) < CesiumMath.EPSILON15) {
return CubicRealPolynomial.computeRealRoots(b, c, d, e);
}
var a3 = b / a;
var a2 = c / a;
var a1 = d / a;
var a0 = e / a;
var k = (a3 < 0.0) ? 1 : 0;
k += (a2 < 0.0) ? k + 1 : k;
k += (a1 < 0.0) ? k + 1 : k;
k += (a0 < 0.0) ? k + 1 : k;
switch (k) {
case 0:
return original(a3, a2, a1, a0);
case 1:
return neumark(a3, a2, a1, a0);
case 2:
return neumark(a3, a2, a1, a0);
case 3:
return original(a3, a2, a1, a0);
case 4:
return original(a3, a2, a1, a0);
case 5:
return neumark(a3, a2, a1, a0);
case 6:
return original(a3, a2, a1, a0);
case 7:
return original(a3, a2, a1, a0);
case 8:
return neumark(a3, a2, a1, a0);
case 9:
return original(a3, a2, a1, a0);
case 10:
return original(a3, a2, a1, a0);
case 11:
return neumark(a3, a2, a1, a0);
case 12:
return original(a3, a2, a1, a0);
case 13:
return original(a3, a2, a1, a0);
case 14:
return original(a3, a2, a1, a0);
case 15:
return original(a3, a2, a1, a0);
default:
return undefined;
}
};
return QuarticRealPolynomial;
});
/*global define*/
define('Core/Ray',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Represents a ray that extends infinitely from the provided origin in the provided direction.
* @alias Ray
* @constructor
*
* @param {Cartesian3} [origin=Cartesian3.ZERO] The origin of the ray.
* @param {Cartesian3} [direction=Cartesian3.ZERO] The direction of the ray.
*/
function Ray(origin, direction) {
direction = Cartesian3.clone(defaultValue(direction, Cartesian3.ZERO));
if (!Cartesian3.equals(direction, Cartesian3.ZERO)) {
Cartesian3.normalize(direction, direction);
}
/**
* The origin of the ray.
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.origin = Cartesian3.clone(defaultValue(origin, Cartesian3.ZERO));
/**
* The direction of the ray.
* @type {Cartesian3}
*/
this.direction = direction;
}
/**
* Computes the point along the ray given by r(t) = o + t*d,
* where o is the origin of the ray and d is the direction.
*
* @param {Ray} ray The ray.
* @param {Number} t A scalar value.
* @param {Cartesian3} [result] The object in which the result will be stored.
* @returns {Cartesian3} The modified result parameter, or a new instance if none was provided.
*
* @example
* //Get the first intersection point of a ray and an ellipsoid.
* var intersection = Cesium.IntersectionTests.rayEllipsoid(ray, ellipsoid);
* var point = Cesium.Ray.getPoint(ray, intersection.start);
*/
Ray.getPoint = function(ray, t, result) {
if (!defined(ray)){
throw new DeveloperError('ray is requred');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is a required number');
}
if (!defined(result)) {
result = new Cartesian3();
}
result = Cartesian3.multiplyByScalar(ray.direction, t, result);
return Cartesian3.add(ray.origin, result, result);
};
return Ray;
});
/*global define*/
define('Core/IntersectionTests',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Math',
'./Matrix3',
'./QuadraticRealPolynomial',
'./QuarticRealPolynomial',
'./Ray'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
DeveloperError,
CesiumMath,
Matrix3,
QuadraticRealPolynomial,
QuarticRealPolynomial,
Ray) {
'use strict';
/**
* Functions for computing the intersection between geometries such as rays, planes, triangles, and ellipsoids.
*
* @exports IntersectionTests
*/
var IntersectionTests = {};
/**
* Computes the intersection of a ray and a plane.
*
* @param {Ray} ray The ray.
* @param {Plane} plane The plane.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The intersection point or undefined if there is no intersections.
*/
IntersectionTests.rayPlane = function(ray, plane, result) {
if (!defined(ray)) {
throw new DeveloperError('ray is required.');
}
if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
if (!defined(result)) {
result = new Cartesian3();
}
var origin = ray.origin;
var direction = ray.direction;
var normal = plane.normal;
var denominator = Cartesian3.dot(normal, direction);
if (Math.abs(denominator) < CesiumMath.EPSILON15) {
// Ray is parallel to plane. The ray may be in the polygon's plane.
return undefined;
}
var t = (-plane.distance - Cartesian3.dot(normal, origin)) / denominator;
if (t < 0) {
return undefined;
}
result = Cartesian3.multiplyByScalar(direction, t, result);
return Cartesian3.add(origin, result, result);
};
var scratchEdge0 = new Cartesian3();
var scratchEdge1 = new Cartesian3();
var scratchPVec = new Cartesian3();
var scratchTVec = new Cartesian3();
var scratchQVec = new Cartesian3();
/**
* Computes the intersection of a ray and a triangle as a parametric distance along the input ray.
*
* Implements {@link https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf|
* Fast Minimum Storage Ray/Triangle Intersection} by Tomas Moller and Ben Trumbore.
*
* @memberof IntersectionTests
*
* @param {Ray} ray The ray.
* @param {Cartesian3} p0 The first vertex of the triangle.
* @param {Cartesian3} p1 The second vertex of the triangle.
* @param {Cartesian3} p2 The third vertex of the triangle.
* @param {Boolean} [cullBackFaces=false] If true
, will only compute an intersection with the front face of the triangle
* and return undefined for intersections with the back face.
* @returns {Number} The intersection as a parametric distance along the ray, or undefined if there is no intersection.
*/
IntersectionTests.rayTriangleParametric = function(ray, p0, p1, p2, cullBackFaces) {
if (!defined(ray)) {
throw new DeveloperError('ray is required.');
}
if (!defined(p0)) {
throw new DeveloperError('p0 is required.');
}
if (!defined(p1)) {
throw new DeveloperError('p1 is required.');
}
if (!defined(p2)) {
throw new DeveloperError('p2 is required.');
}
cullBackFaces = defaultValue(cullBackFaces, false);
var origin = ray.origin;
var direction = ray.direction;
var edge0 = Cartesian3.subtract(p1, p0, scratchEdge0);
var edge1 = Cartesian3.subtract(p2, p0, scratchEdge1);
var p = Cartesian3.cross(direction, edge1, scratchPVec);
var det = Cartesian3.dot(edge0, p);
var tvec;
var q;
var u;
var v;
var t;
if (cullBackFaces) {
if (det < CesiumMath.EPSILON6) {
return undefined;
}
tvec = Cartesian3.subtract(origin, p0, scratchTVec);
u = Cartesian3.dot(tvec, p);
if (u < 0.0 || u > det) {
return undefined;
}
q = Cartesian3.cross(tvec, edge0, scratchQVec);
v = Cartesian3.dot(direction, q);
if (v < 0.0 || u + v > det) {
return undefined;
}
t = Cartesian3.dot(edge1, q) / det;
} else {
if (Math.abs(det) < CesiumMath.EPSILON6) {
return undefined;
}
var invDet = 1.0 / det;
tvec = Cartesian3.subtract(origin, p0, scratchTVec);
u = Cartesian3.dot(tvec, p) * invDet;
if (u < 0.0 || u > 1.0) {
return undefined;
}
q = Cartesian3.cross(tvec, edge0, scratchQVec);
v = Cartesian3.dot(direction, q) * invDet;
if (v < 0.0 || u + v > 1.0) {
return undefined;
}
t = Cartesian3.dot(edge1, q) * invDet;
}
return t;
};
/**
* Computes the intersection of a ray and a triangle as a Cartesian3 coordinate.
*
* Implements {@link https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf|
* Fast Minimum Storage Ray/Triangle Intersection} by Tomas Moller and Ben Trumbore.
*
* @memberof IntersectionTests
*
* @param {Ray} ray The ray.
* @param {Cartesian3} p0 The first vertex of the triangle.
* @param {Cartesian3} p1 The second vertex of the triangle.
* @param {Cartesian3} p2 The third vertex of the triangle.
* @param {Boolean} [cullBackFaces=false] If true
, will only compute an intersection with the front face of the triangle
* and return undefined for intersections with the back face.
* @param {Cartesian3} [result] The Cartesian3
onto which to store the result.
* @returns {Cartesian3} The intersection point or undefined if there is no intersections.
*/
IntersectionTests.rayTriangle = function(ray, p0, p1, p2, cullBackFaces, result) {
var t = IntersectionTests.rayTriangleParametric(ray, p0, p1, p2, cullBackFaces);
if (!defined(t) || t < 0.0) {
return undefined;
}
if (!defined(result)) {
result = new Cartesian3();
}
Cartesian3.multiplyByScalar(ray.direction, t, result);
return Cartesian3.add(ray.origin, result, result);
};
var scratchLineSegmentTriangleRay = new Ray();
/**
* Computes the intersection of a line segment and a triangle.
* @memberof IntersectionTests
*
* @param {Cartesian3} v0 The an end point of the line segment.
* @param {Cartesian3} v1 The other end point of the line segment.
* @param {Cartesian3} p0 The first vertex of the triangle.
* @param {Cartesian3} p1 The second vertex of the triangle.
* @param {Cartesian3} p2 The third vertex of the triangle.
* @param {Boolean} [cullBackFaces=false] If true
, will only compute an intersection with the front face of the triangle
* and return undefined for intersections with the back face.
* @param {Cartesian3} [result] The Cartesian3
onto which to store the result.
* @returns {Cartesian3} The intersection point or undefined if there is no intersections.
*/
IntersectionTests.lineSegmentTriangle = function(v0, v1, p0, p1, p2, cullBackFaces, result) {
if (!defined(v0)) {
throw new DeveloperError('v0 is required.');
}
if (!defined(v1)) {
throw new DeveloperError('v1 is required.');
}
if (!defined(p0)) {
throw new DeveloperError('p0 is required.');
}
if (!defined(p1)) {
throw new DeveloperError('p1 is required.');
}
if (!defined(p2)) {
throw new DeveloperError('p2 is required.');
}
var ray = scratchLineSegmentTriangleRay;
Cartesian3.clone(v0, ray.origin);
Cartesian3.subtract(v1, v0, ray.direction);
Cartesian3.normalize(ray.direction, ray.direction);
var t = IntersectionTests.rayTriangleParametric(ray, p0, p1, p2, cullBackFaces);
if (!defined(t) || t < 0.0 || t > Cartesian3.distance(v0, v1)) {
return undefined;
}
if (!defined(result)) {
result = new Cartesian3();
}
Cartesian3.multiplyByScalar(ray.direction, t, result);
return Cartesian3.add(ray.origin, result, result);
};
function solveQuadratic(a, b, c, result) {
var det = b * b - 4.0 * a * c;
if (det < 0.0) {
return undefined;
} else if (det > 0.0) {
var denom = 1.0 / (2.0 * a);
var disc = Math.sqrt(det);
var root0 = (-b + disc) * denom;
var root1 = (-b - disc) * denom;
if (root0 < root1) {
result.root0 = root0;
result.root1 = root1;
} else {
result.root0 = root1;
result.root1 = root0;
}
return result;
}
var root = -b / (2.0 * a);
if (root === 0.0) {
return undefined;
}
result.root0 = result.root1 = root;
return result;
}
var raySphereRoots = {
root0 : 0.0,
root1 : 0.0
};
function raySphere(ray, sphere, result) {
if (!defined(result)) {
result = {};
}
var origin = ray.origin;
var direction = ray.direction;
var center = sphere.center;
var radiusSquared = sphere.radius * sphere.radius;
var diff = Cartesian3.subtract(origin, center, scratchPVec);
var a = Cartesian3.dot(direction, direction);
var b = 2.0 * Cartesian3.dot(direction, diff);
var c = Cartesian3.magnitudeSquared(diff) - radiusSquared;
var roots = solveQuadratic(a, b, c, raySphereRoots);
if (!defined(roots)) {
return undefined;
}
result.start = roots.root0;
result.stop = roots.root1;
return result;
}
/**
* Computes the intersection points of a ray with a sphere.
* @memberof IntersectionTests
*
* @param {Ray} ray The ray.
* @param {BoundingSphere} sphere The sphere.
* @param {Object} [result] The result onto which to store the result.
* @returns {Object} An object with the first (start
) and the second (stop
) intersection scalars for points along the ray or undefined if there are no intersections.
*/
IntersectionTests.raySphere = function(ray, sphere, result) {
if (!defined(ray)) {
throw new DeveloperError('ray is required.');
}
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
result = raySphere(ray, sphere, result);
if (!defined(result) || result.stop < 0.0) {
return undefined;
}
result.start = Math.max(result.start, 0.0);
return result;
};
var scratchLineSegmentRay = new Ray();
/**
* Computes the intersection points of a line segment with a sphere.
* @memberof IntersectionTests
*
* @param {Cartesian3} p0 An end point of the line segment.
* @param {Cartesian3} p1 The other end point of the line segment.
* @param {BoundingSphere} sphere The sphere.
* @param {Object} [result] The result onto which to store the result.
* @returns {Object} An object with the first (start
) and the second (stop
) intersection scalars for points along the line segment or undefined if there are no intersections.
*/
IntersectionTests.lineSegmentSphere = function(p0, p1, sphere, result) {
if (!defined(p0)) {
throw new DeveloperError('p0 is required.');
}
if (!defined(p1)) {
throw new DeveloperError('p1 is required.');
}
if (!defined(sphere)) {
throw new DeveloperError('sphere is required.');
}
var ray = scratchLineSegmentRay;
Cartesian3.clone(p0, ray.origin);
var direction = Cartesian3.subtract(p1, p0, ray.direction);
var maxT = Cartesian3.magnitude(direction);
Cartesian3.normalize(direction, direction);
result = raySphere(ray, sphere, result);
if (!defined(result) || result.stop < 0.0 || result.start > maxT) {
return undefined;
}
result.start = Math.max(result.start, 0.0);
result.stop = Math.min(result.stop, maxT);
return result;
};
var scratchQ = new Cartesian3();
var scratchW = new Cartesian3();
/**
* Computes the intersection points of a ray with an ellipsoid.
*
* @param {Ray} ray The ray.
* @param {Ellipsoid} ellipsoid The ellipsoid.
* @returns {Object} An object with the first (start
) and the second (stop
) intersection scalars for points along the ray or undefined if there are no intersections.
*/
IntersectionTests.rayEllipsoid = function(ray, ellipsoid) {
if (!defined(ray)) {
throw new DeveloperError('ray is required.');
}
if (!defined(ellipsoid)) {
throw new DeveloperError('ellipsoid is required.');
}
var inverseRadii = ellipsoid.oneOverRadii;
var q = Cartesian3.multiplyComponents(inverseRadii, ray.origin, scratchQ);
var w = Cartesian3.multiplyComponents(inverseRadii, ray.direction, scratchW);
var q2 = Cartesian3.magnitudeSquared(q);
var qw = Cartesian3.dot(q, w);
var difference, w2, product, discriminant, temp;
if (q2 > 1.0) {
// Outside ellipsoid.
if (qw >= 0.0) {
// Looking outward or tangent (0 intersections).
return undefined;
}
// qw < 0.0.
var qw2 = qw * qw;
difference = q2 - 1.0; // Positively valued.
w2 = Cartesian3.magnitudeSquared(w);
product = w2 * difference;
if (qw2 < product) {
// Imaginary roots (0 intersections).
return undefined;
} else if (qw2 > product) {
// Distinct roots (2 intersections).
discriminant = qw * qw - product;
temp = -qw + Math.sqrt(discriminant); // Avoid cancellation.
var root0 = temp / w2;
var root1 = difference / temp;
if (root0 < root1) {
return {
start : root0,
stop : root1
};
}
return {
start : root1,
stop : root0
};
} else {
// qw2 == product. Repeated roots (2 intersections).
var root = Math.sqrt(difference / w2);
return {
start : root,
stop : root
};
}
} else if (q2 < 1.0) {
// Inside ellipsoid (2 intersections).
difference = q2 - 1.0; // Negatively valued.
w2 = Cartesian3.magnitudeSquared(w);
product = w2 * difference; // Negatively valued.
discriminant = qw * qw - product;
temp = -qw + Math.sqrt(discriminant); // Positively valued.
return {
start : 0.0,
stop : temp / w2
};
} else {
// q2 == 1.0. On ellipsoid.
if (qw < 0.0) {
// Looking inward.
w2 = Cartesian3.magnitudeSquared(w);
return {
start : 0.0,
stop : -qw / w2
};
}
// qw >= 0.0. Looking outward or tangent.
return undefined;
}
};
function addWithCancellationCheck(left, right, tolerance) {
var difference = left + right;
if ((CesiumMath.sign(left) !== CesiumMath.sign(right)) &&
Math.abs(difference / Math.max(Math.abs(left), Math.abs(right))) < tolerance) {
return 0.0;
}
return difference;
}
function quadraticVectorExpression(A, b, c, x, w) {
var xSquared = x * x;
var wSquared = w * w;
var l2 = (A[Matrix3.COLUMN1ROW1] - A[Matrix3.COLUMN2ROW2]) * wSquared;
var l1 = w * (x * addWithCancellationCheck(A[Matrix3.COLUMN1ROW0], A[Matrix3.COLUMN0ROW1], CesiumMath.EPSILON15) + b.y);
var l0 = (A[Matrix3.COLUMN0ROW0] * xSquared + A[Matrix3.COLUMN2ROW2] * wSquared) + x * b.x + c;
var r1 = wSquared * addWithCancellationCheck(A[Matrix3.COLUMN2ROW1], A[Matrix3.COLUMN1ROW2], CesiumMath.EPSILON15);
var r0 = w * (x * addWithCancellationCheck(A[Matrix3.COLUMN2ROW0], A[Matrix3.COLUMN0ROW2]) + b.z);
var cosines;
var solutions = [];
if (r0 === 0.0 && r1 === 0.0) {
cosines = QuadraticRealPolynomial.computeRealRoots(l2, l1, l0);
if (cosines.length === 0) {
return solutions;
}
var cosine0 = cosines[0];
var sine0 = Math.sqrt(Math.max(1.0 - cosine0 * cosine0, 0.0));
solutions.push(new Cartesian3(x, w * cosine0, w * -sine0));
solutions.push(new Cartesian3(x, w * cosine0, w * sine0));
if (cosines.length === 2) {
var cosine1 = cosines[1];
var sine1 = Math.sqrt(Math.max(1.0 - cosine1 * cosine1, 0.0));
solutions.push(new Cartesian3(x, w * cosine1, w * -sine1));
solutions.push(new Cartesian3(x, w * cosine1, w * sine1));
}
return solutions;
}
var r0Squared = r0 * r0;
var r1Squared = r1 * r1;
var l2Squared = l2 * l2;
var r0r1 = r0 * r1;
var c4 = l2Squared + r1Squared;
var c3 = 2.0 * (l1 * l2 + r0r1);
var c2 = 2.0 * l0 * l2 + l1 * l1 - r1Squared + r0Squared;
var c1 = 2.0 * (l0 * l1 - r0r1);
var c0 = l0 * l0 - r0Squared;
if (c4 === 0.0 && c3 === 0.0 && c2 === 0.0 && c1 === 0.0) {
return solutions;
}
cosines = QuarticRealPolynomial.computeRealRoots(c4, c3, c2, c1, c0);
var length = cosines.length;
if (length === 0) {
return solutions;
}
for ( var i = 0; i < length; ++i) {
var cosine = cosines[i];
var cosineSquared = cosine * cosine;
var sineSquared = Math.max(1.0 - cosineSquared, 0.0);
var sine = Math.sqrt(sineSquared);
//var left = l2 * cosineSquared + l1 * cosine + l0;
var left;
if (CesiumMath.sign(l2) === CesiumMath.sign(l0)) {
left = addWithCancellationCheck(l2 * cosineSquared + l0, l1 * cosine, CesiumMath.EPSILON12);
} else if (CesiumMath.sign(l0) === CesiumMath.sign(l1 * cosine)) {
left = addWithCancellationCheck(l2 * cosineSquared, l1 * cosine + l0, CesiumMath.EPSILON12);
} else {
left = addWithCancellationCheck(l2 * cosineSquared + l1 * cosine, l0, CesiumMath.EPSILON12);
}
var right = addWithCancellationCheck(r1 * cosine, r0, CesiumMath.EPSILON15);
var product = left * right;
if (product < 0.0) {
solutions.push(new Cartesian3(x, w * cosine, w * sine));
} else if (product > 0.0) {
solutions.push(new Cartesian3(x, w * cosine, w * -sine));
} else if (sine !== 0.0) {
solutions.push(new Cartesian3(x, w * cosine, w * -sine));
solutions.push(new Cartesian3(x, w * cosine, w * sine));
++i;
} else {
solutions.push(new Cartesian3(x, w * cosine, w * sine));
}
}
return solutions;
}
var firstAxisScratch = new Cartesian3();
var secondAxisScratch = new Cartesian3();
var thirdAxisScratch = new Cartesian3();
var referenceScratch = new Cartesian3();
var bCart = new Cartesian3();
var bScratch = new Matrix3();
var btScratch = new Matrix3();
var diScratch = new Matrix3();
var dScratch = new Matrix3();
var cScratch = new Matrix3();
var tempMatrix = new Matrix3();
var aScratch = new Matrix3();
var sScratch = new Cartesian3();
var closestScratch = new Cartesian3();
var surfPointScratch = new Cartographic();
/**
* Provides the point along the ray which is nearest to the ellipsoid.
*
* @param {Ray} ray The ray.
* @param {Ellipsoid} ellipsoid The ellipsoid.
* @returns {Cartesian3} The nearest planetodetic point on the ray.
*/
IntersectionTests.grazingAltitudeLocation = function(ray, ellipsoid) {
if (!defined(ray)) {
throw new DeveloperError('ray is required.');
}
if (!defined(ellipsoid)) {
throw new DeveloperError('ellipsoid is required.');
}
var position = ray.origin;
var direction = ray.direction;
if (!Cartesian3.equals(position, Cartesian3.ZERO)) {
var normal = ellipsoid.geodeticSurfaceNormal(position, firstAxisScratch);
if (Cartesian3.dot(direction, normal) >= 0.0) { // The location provided is the closest point in altitude
return position;
}
}
var intersects = defined(this.rayEllipsoid(ray, ellipsoid));
// Compute the scaled direction vector.
var f = ellipsoid.transformPositionToScaledSpace(direction, firstAxisScratch);
// Constructs a basis from the unit scaled direction vector. Construct its rotation and transpose.
var firstAxis = Cartesian3.normalize(f, f);
var reference = Cartesian3.mostOrthogonalAxis(f, referenceScratch);
var secondAxis = Cartesian3.normalize(Cartesian3.cross(reference, firstAxis, secondAxisScratch), secondAxisScratch);
var thirdAxis = Cartesian3.normalize(Cartesian3.cross(firstAxis, secondAxis, thirdAxisScratch), thirdAxisScratch);
var B = bScratch;
B[0] = firstAxis.x;
B[1] = firstAxis.y;
B[2] = firstAxis.z;
B[3] = secondAxis.x;
B[4] = secondAxis.y;
B[5] = secondAxis.z;
B[6] = thirdAxis.x;
B[7] = thirdAxis.y;
B[8] = thirdAxis.z;
var B_T = Matrix3.transpose(B, btScratch);
// Get the scaling matrix and its inverse.
var D_I = Matrix3.fromScale(ellipsoid.radii, diScratch);
var D = Matrix3.fromScale(ellipsoid.oneOverRadii, dScratch);
var C = cScratch;
C[0] = 0.0;
C[1] = -direction.z;
C[2] = direction.y;
C[3] = direction.z;
C[4] = 0.0;
C[5] = -direction.x;
C[6] = -direction.y;
C[7] = direction.x;
C[8] = 0.0;
var temp = Matrix3.multiply(Matrix3.multiply(B_T, D, tempMatrix), C, tempMatrix);
var A = Matrix3.multiply(Matrix3.multiply(temp, D_I, aScratch), B, aScratch);
var b = Matrix3.multiplyByVector(temp, position, bCart);
// Solve for the solutions to the expression in standard form:
var solutions = quadraticVectorExpression(A, Cartesian3.negate(b, firstAxisScratch), 0.0, 0.0, 1.0);
var s;
var altitude;
var length = solutions.length;
if (length > 0) {
var closest = Cartesian3.clone(Cartesian3.ZERO, closestScratch);
var maximumValue = Number.NEGATIVE_INFINITY;
for ( var i = 0; i < length; ++i) {
s = Matrix3.multiplyByVector(D_I, Matrix3.multiplyByVector(B, solutions[i], sScratch), sScratch);
var v = Cartesian3.normalize(Cartesian3.subtract(s, position, referenceScratch), referenceScratch);
var dotProduct = Cartesian3.dot(v, direction);
if (dotProduct > maximumValue) {
maximumValue = dotProduct;
closest = Cartesian3.clone(s, closest);
}
}
var surfacePoint = ellipsoid.cartesianToCartographic(closest, surfPointScratch);
maximumValue = CesiumMath.clamp(maximumValue, 0.0, 1.0);
altitude = Cartesian3.magnitude(Cartesian3.subtract(closest, position, referenceScratch)) * Math.sqrt(1.0 - maximumValue * maximumValue);
altitude = intersects ? -altitude : altitude;
surfacePoint.height = altitude;
return ellipsoid.cartographicToCartesian(surfacePoint, new Cartesian3());
}
return undefined;
};
var lineSegmentPlaneDifference = new Cartesian3();
/**
* Computes the intersection of a line segment and a plane.
*
* @param {Cartesian3} endPoint0 An end point of the line segment.
* @param {Cartesian3} endPoint1 The other end point of the line segment.
* @param {Plane} plane The plane.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The intersection point or undefined if there is no intersection.
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
* var normal = ellipsoid.geodeticSurfaceNormal(origin);
* var plane = Cesium.Plane.fromPointNormal(origin, normal);
*
* var p0 = new Cesium.Cartesian3(...);
* var p1 = new Cesium.Cartesian3(...);
*
* // find the intersection of the line segment from p0 to p1 and the tangent plane at origin.
* var intersection = Cesium.IntersectionTests.lineSegmentPlane(p0, p1, plane);
*/
IntersectionTests.lineSegmentPlane = function(endPoint0, endPoint1, plane, result) {
if (!defined(endPoint0)) {
throw new DeveloperError('endPoint0 is required.');
}
if (!defined(endPoint1)) {
throw new DeveloperError('endPoint1 is required.');
}
if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
if (!defined(result)) {
result = new Cartesian3();
}
var difference = Cartesian3.subtract(endPoint1, endPoint0, lineSegmentPlaneDifference);
var normal = plane.normal;
var nDotDiff = Cartesian3.dot(normal, difference);
// check if the segment and plane are parallel
if (Math.abs(nDotDiff) < CesiumMath.EPSILON6) {
return undefined;
}
var nDotP0 = Cartesian3.dot(normal, endPoint0);
var t = -(plane.distance + nDotP0) / nDotDiff;
// intersection only if t is in [0, 1]
if (t < 0.0 || t > 1.0) {
return undefined;
}
// intersection is endPoint0 + t * (endPoint1 - endPoint0)
Cartesian3.multiplyByScalar(difference, t, result);
Cartesian3.add(endPoint0, result, result);
return result;
};
/**
* Computes the intersection of a triangle and a plane
*
* @param {Cartesian3} p0 First point of the triangle
* @param {Cartesian3} p1 Second point of the triangle
* @param {Cartesian3} p2 Third point of the triangle
* @param {Plane} plane Intersection plane
* @returns {Object} An object with properties positions
and indices
, which are arrays that represent three triangles that do not cross the plane. (Undefined if no intersection exists)
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
* var normal = ellipsoid.geodeticSurfaceNormal(origin);
* var plane = Cesium.Plane.fromPointNormal(origin, normal);
*
* var p0 = new Cesium.Cartesian3(...);
* var p1 = new Cesium.Cartesian3(...);
* var p2 = new Cesium.Cartesian3(...);
*
* // convert the triangle composed of points (p0, p1, p2) to three triangles that don't cross the plane
* var triangles = Cesium.IntersectionTests.trianglePlaneIntersection(p0, p1, p2, plane);
*/
IntersectionTests.trianglePlaneIntersection = function(p0, p1, p2, plane) {
if ((!defined(p0)) ||
(!defined(p1)) ||
(!defined(p2)) ||
(!defined(plane))) {
throw new DeveloperError('p0, p1, p2, and plane are required.');
}
var planeNormal = plane.normal;
var planeD = plane.distance;
var p0Behind = (Cartesian3.dot(planeNormal, p0) + planeD) < 0.0;
var p1Behind = (Cartesian3.dot(planeNormal, p1) + planeD) < 0.0;
var p2Behind = (Cartesian3.dot(planeNormal, p2) + planeD) < 0.0;
// Given these dots products, the calls to lineSegmentPlaneIntersection
// always have defined results.
var numBehind = 0;
numBehind += p0Behind ? 1 : 0;
numBehind += p1Behind ? 1 : 0;
numBehind += p2Behind ? 1 : 0;
var u1, u2;
if (numBehind === 1 || numBehind === 2) {
u1 = new Cartesian3();
u2 = new Cartesian3();
}
if (numBehind === 1) {
if (p0Behind) {
IntersectionTests.lineSegmentPlane(p0, p1, plane, u1);
IntersectionTests.lineSegmentPlane(p0, p2, plane, u2);
return {
positions : [p0, p1, p2, u1, u2 ],
indices : [
// Behind
0, 3, 4,
// In front
1, 2, 4,
1, 4, 3
]
};
} else if (p1Behind) {
IntersectionTests.lineSegmentPlane(p1, p2, plane, u1);
IntersectionTests.lineSegmentPlane(p1, p0, plane, u2);
return {
positions : [p0, p1, p2, u1, u2 ],
indices : [
// Behind
1, 3, 4,
// In front
2, 0, 4,
2, 4, 3
]
};
} else if (p2Behind) {
IntersectionTests.lineSegmentPlane(p2, p0, plane, u1);
IntersectionTests.lineSegmentPlane(p2, p1, plane, u2);
return {
positions : [p0, p1, p2, u1, u2 ],
indices : [
// Behind
2, 3, 4,
// In front
0, 1, 4,
0, 4, 3
]
};
}
} else if (numBehind === 2) {
if (!p0Behind) {
IntersectionTests.lineSegmentPlane(p1, p0, plane, u1);
IntersectionTests.lineSegmentPlane(p2, p0, plane, u2);
return {
positions : [p0, p1, p2, u1, u2 ],
indices : [
// Behind
1, 2, 4,
1, 4, 3,
// In front
0, 3, 4
]
};
} else if (!p1Behind) {
IntersectionTests.lineSegmentPlane(p2, p1, plane, u1);
IntersectionTests.lineSegmentPlane(p0, p1, plane, u2);
return {
positions : [p0, p1, p2, u1, u2 ],
indices : [
// Behind
2, 0, 4,
2, 4, 3,
// In front
1, 3, 4
]
};
} else if (!p2Behind) {
IntersectionTests.lineSegmentPlane(p0, p2, plane, u1);
IntersectionTests.lineSegmentPlane(p1, p2, plane, u2);
return {
positions : [p0, p1, p2, u1, u2 ],
indices : [
// Behind
0, 1, 4,
0, 4, 3,
// In front
2, 3, 4
]
};
}
}
// if numBehind is 3, the triangle is completely behind the plane;
// otherwise, it is completely in front (numBehind is 0).
return undefined;
};
return IntersectionTests;
});
/*global define*/
define('Core/Plane',[
'./Cartesian3',
'./defined',
'./DeveloperError',
'./freezeObject'
], function(
Cartesian3,
defined,
DeveloperError,
freezeObject) {
'use strict';
/**
* A plane in Hessian Normal Form defined by
*
* ax + by + cz + d = 0
*
* where (a, b, c) is the plane's normal
, d is the signed
* distance
to the plane, and (x, y, z) is any point on
* the plane.
*
* @alias Plane
* @constructor
*
* @param {Cartesian3} normal The plane's normal (normalized).
* @param {Number} distance The shortest distance from the origin to the plane. The sign of
* distance
determines which side of the plane the origin
* is on. If distance
is positive, the origin is in the half-space
* in the direction of the normal; if negative, the origin is in the half-space
* opposite to the normal; if zero, the plane passes through the origin.
*
* @example
* // The plane x=0
* var plane = new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0.0);
*/
function Plane(normal, distance) {
if (!defined(normal)) {
throw new DeveloperError('normal is required.');
}
if (!defined(distance)) {
throw new DeveloperError('distance is required.');
}
/**
* The plane's normal.
*
* @type {Cartesian3}
*/
this.normal = Cartesian3.clone(normal);
/**
* The shortest distance from the origin to the plane. The sign of
* distance
determines which side of the plane the origin
* is on. If distance
is positive, the origin is in the half-space
* in the direction of the normal; if negative, the origin is in the half-space
* opposite to the normal; if zero, the plane passes through the origin.
*
* @type {Number}
*/
this.distance = distance;
}
/**
* Creates a plane from a normal and a point on the plane.
*
* @param {Cartesian3} point The point on the plane.
* @param {Cartesian3} normal The plane's normal (normalized).
* @param {Plane} [result] The object onto which to store the result.
* @returns {Plane} A new plane instance or the modified result parameter.
*
* @example
* var point = Cesium.Cartesian3.fromDegrees(-72.0, 40.0);
* var normal = ellipsoid.geodeticSurfaceNormal(point);
* var tangentPlane = Cesium.Plane.fromPointNormal(point, normal);
*/
Plane.fromPointNormal = function(point, normal, result) {
if (!defined(point)) {
throw new DeveloperError('point is required.');
}
if (!defined(normal)) {
throw new DeveloperError('normal is required.');
}
var distance = -Cartesian3.dot(normal, point);
if (!defined(result)) {
return new Plane(normal, distance);
}
Cartesian3.clone(normal, result.normal);
result.distance = distance;
return result;
};
var scratchNormal = new Cartesian3();
/**
* Creates a plane from the general equation
*
* @param {Cartesian4} coefficients The plane's normal (normalized).
* @param {Plane} [result] The object onto which to store the result.
* @returns {Plane} A new plane instance or the modified result parameter.
*/
Plane.fromCartesian4 = function(coefficients, result) {
if (!defined(coefficients)) {
throw new DeveloperError('coefficients is required.');
}
var normal = Cartesian3.fromCartesian4(coefficients, scratchNormal);
var distance = coefficients.w;
if (!defined(result)) {
return new Plane(normal, distance);
} else {
Cartesian3.clone(normal, result.normal);
result.distance = distance;
return result;
}
};
/**
* Computes the signed shortest distance of a point to a plane.
* The sign of the distance determines which side of the plane the point
* is on. If the distance is positive, the point is in the half-space
* in the direction of the normal; if negative, the point is in the half-space
* opposite to the normal; if zero, the plane passes through the point.
*
* @param {Plane} plane The plane.
* @param {Cartesian3} point The point.
* @returns {Number} The signed shortest distance of the point to the plane.
*/
Plane.getPointDistance = function(plane, point) {
if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
if (!defined(point)) {
throw new DeveloperError('point is required.');
}
return Cartesian3.dot(plane.normal, point) + plane.distance;
};
/**
* A constant initialized to the XY plane passing through the origin, with normal in positive Z.
*
* @type {Plane}
* @constant
*/
Plane.ORIGIN_XY_PLANE = freezeObject(new Plane(Cartesian3.UNIT_Z, 0.0));
/**
* A constant initialized to the YZ plane passing through the origin, with normal in positive X.
*
* @type {Plane}
* @constant
*/
Plane.ORIGIN_YZ_PLANE = freezeObject(new Plane(Cartesian3.UNIT_X, 0.0));
/**
* A constant initialized to the ZX plane passing through the origin, with normal in positive Y.
*
* @type {Plane}
* @constant
*/
Plane.ORIGIN_ZX_PLANE = freezeObject(new Plane(Cartesian3.UNIT_Y, 0.0));
return Plane;
});
/*global define*/
define('Core/oneTimeWarning',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
"use strict";
var warnings = {};
/**
* Logs a one time message to the console. Use this function instead of
* console.log
directly since this does not log duplicate messages
* unless it is called from multiple workers.
*
* @exports oneTimeWarning
*
* @param {String} identifier The unique identifier for this warning.
* @param {String} [message=identifier] The message to log to the console.
*
* @example
* for(var i=0;iconsole.log
directly since this does not log duplicate messages
* unless it is called from multiple workers.
*
* @exports deprecationWarning
*
* @param {String} identifier The unique identifier for this deprecated API.
* @param {String} message The message to log to the console.
*
* @example
* // Deprecated function or class
* function Foo() {
* deprecationWarning('Foo', 'Foo was deprecated in Cesium 1.01. It will be removed in 1.03. Use newFoo instead.');
* // ...
* }
*
* // Deprecated function
* Bar.prototype.func = function() {
* deprecationWarning('Bar.func', 'Bar.func() was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newFunc() instead.');
* // ...
* };
*
* // Deprecated property
* defineProperties(Bar.prototype, {
* prop : {
* get : function() {
* deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newProp instead.');
* // ...
* },
* set : function(value) {
* deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newProp instead.');
* // ...
* }
* }
* });
*
* @private
*/
function deprecationWarning(identifier, message) {
if (!defined(identifier) || !defined(message)) {
throw new DeveloperError('identifier and message are required.');
}
oneTimeWarning(identifier, message);
}
return deprecationWarning;
});
/*global define*/
define('Core/binarySearch',[
'./defined',
'./DeveloperError'
], function(
defined,
DeveloperError) {
'use strict';
/**
* Finds an item in a sorted array.
*
* @exports binarySearch
*
* @param {Array} array The sorted array to search.
* @param {Object} itemToFind The item to find in the array.
* @param {binarySearch~Comparator} comparator The function to use to compare the item to
* elements in the array.
* @returns {Number} The index of itemToFind
in the array, if it exists. If itemToFind
* does not exist, the return value is a negative number which is the bitwise complement (~)
* of the index before which the itemToFind should be inserted in order to maintain the
* sorted order of the array.
*
* @example
* // Create a comparator function to search through an array of numbers.
* function comparator(a, b) {
* return a - b;
* };
* var numbers = [0, 2, 4, 6, 8];
* var index = Cesium.binarySearch(numbers, 6, comparator); // 3
*/
function binarySearch(array, itemToFind, comparator) {
if (!defined(array)) {
throw new DeveloperError('array is required.');
}
if (!defined(itemToFind)) {
throw new DeveloperError('itemToFind is required.');
}
if (!defined(comparator)) {
throw new DeveloperError('comparator is required.');
}
var low = 0;
var high = array.length - 1;
var i;
var comparison;
while (low <= high) {
i = ~~((low + high) / 2);
comparison = comparator(array[i], itemToFind);
if (comparison < 0) {
low = i + 1;
continue;
}
if (comparison > 0) {
high = i - 1;
continue;
}
return i;
}
return ~(high + 1);
}
/**
* A function used to compare two items while performing a binary search.
* @callback binarySearch~Comparator
*
* @param {Object} a An item in the array.
* @param {Object} b The item being searched for.
* @returns {Number} Returns a negative value if a
is less than b
,
* a positive value if a
is greater than b
, or
* 0 if a
is equal to b
.
*
* @example
* function compareNumbers(a, b) {
* return a - b;
* }
*/
return binarySearch;
});
/*global define*/
define('Core/EarthOrientationParametersSample',[],function() {
'use strict';
/**
* A set of Earth Orientation Parameters (EOP) sampled at a time.
*
* @alias EarthOrientationParametersSample
* @constructor
*
* @param {Number} xPoleWander The pole wander about the X axis, in radians.
* @param {Number} yPoleWander The pole wander about the Y axis, in radians.
* @param {Number} xPoleOffset The offset to the Celestial Intermediate Pole (CIP) about the X axis, in radians.
* @param {Number} yPoleOffset The offset to the Celestial Intermediate Pole (CIP) about the Y axis, in radians.
* @param {Number} ut1MinusUtc The difference in time standards, UT1 - UTC, in seconds.
*
* @private
*/
function EarthOrientationParametersSample(xPoleWander, yPoleWander, xPoleOffset, yPoleOffset, ut1MinusUtc) {
/**
* The pole wander about the X axis, in radians.
* @type {Number}
*/
this.xPoleWander = xPoleWander;
/**
* The pole wander about the Y axis, in radians.
* @type {Number}
*/
this.yPoleWander = yPoleWander;
/**
* The offset to the Celestial Intermediate Pole (CIP) about the X axis, in radians.
* @type {Number}
*/
this.xPoleOffset = xPoleOffset;
/**
* The offset to the Celestial Intermediate Pole (CIP) about the Y axis, in radians.
* @type {Number}
*/
this.yPoleOffset = yPoleOffset;
/**
* The difference in time standards, UT1 - UTC, in seconds.
* @type {Number}
*/
this.ut1MinusUtc = ut1MinusUtc;
}
return EarthOrientationParametersSample;
});
/**
@license
sprintf.js from the php.js project - https://github.com/kvz/phpjs
Directly from https://github.com/kvz/phpjs/blob/master/functions/strings/sprintf.js
php.js is copyright 2012 Kevin van Zonneveld.
Portions copyright Brett Zamir (http://brett-zamir.me), Kevin van Zonneveld
(http://kevin.vanzonneveld.net), Onno Marsman, Theriault, Michael White
(http://getsprink.com), Waldo Malqui Silva, Paulo Freitas, Jack, Jonas
Raoni Soares Silva (http://www.jsfromhell.com), Philip Peterson, Legaev
Andrey, Ates Goral (http://magnetiq.com), Alex, Ratheous, Martijn Wieringa,
Rafa? Kukawski (http://blog.kukawski.pl), lmeyrick
(https://sourceforge.net/projects/bcmath-js/), Nate, Philippe Baumann,
Enrique Gonzalez, Webtoolkit.info (http://www.webtoolkit.info/), Carlos R.
L. Rodrigues (http://www.jsfromhell.com), Ash Searle
(http://hexmen.com/blog/), Jani Hartikainen, travc, Ole Vrijenhoek,
Erkekjetter, Michael Grier, Rafa? Kukawski (http://kukawski.pl), Johnny
Mast (http://www.phpvrouwen.nl), T.Wild, d3x,
http://stackoverflow.com/questions/57803/how-to-convert-decimal-to-hex-in-javascript,
Rafa? Kukawski (http://blog.kukawski.pl/), stag019, pilus, WebDevHobo
(http://webdevhobo.blogspot.com/), marrtins, GeekFG
(http://geekfg.blogspot.com), Andrea Giammarchi
(http://webreflection.blogspot.com), Arpad Ray (mailto:arpad@php.net),
gorthaur, Paul Smith, Tim de Koning (http://www.kingsquare.nl), Joris, Oleg
Eremeev, Steve Hilder, majak, gettimeofday, KELAN, Josh Fraser
(http://onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/),
Marc Palau, Martin
(http://www.erlenwiese.de/), Breaking Par Consulting Inc
(http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256CFB006C45F7),
Chris, Mirek Slugen, saulius, Alfonso Jimenez
(http://www.alfonsojimenez.com), Diplom@t (http://difane.com/), felix,
Mailfaker (http://www.weedem.fr/), Tyler Akins (http://rumkin.com), Caio
Ariede (http://caioariede.com), Robin, Kankrelune
(http://www.webfaktory.info/), Karol Kowalski, Imgen Tata
(http://www.myipdf.com/), mdsjack (http://www.mdsjack.bo.it), Dreamer,
Felix Geisendoerfer (http://www.debuggable.com/felix), Lars Fischer, AJ,
David, Aman Gupta, Michael White, Public Domain
(http://www.json.org/json2.js), Steven Levithan
(http://blog.stevenlevithan.com), Sakimori, Pellentesque Malesuada,
Thunder.m, Dj (http://phpjs.org/functions/htmlentities:425#comment_134018),
Steve Clay, David James, Francois, class_exists, nobbler, T. Wild, Itsacon
(http://www.itsacon.net/), date, Ole Vrijenhoek (http://www.nervous.nl/),
Fox, Raphael (Ao RUDLER), Marco, noname, Mateusz "loonquawl" Zalega, Frank
Forte, Arno, ger, mktime, john (http://www.jd-tech.net), Nick Kolosov
(http://sammy.ru), marc andreu, Scott Cariss, Douglas Crockford
(http://javascript.crockford.com), madipta, Slawomir Kaniecki,
ReverseSyntax, Nathan, Alex Wilson, kenneth, Bayron Guevara, Adam Wallner
(http://web2.bitbaro.hu/), paulo kuong, jmweb, Lincoln Ramsay, djmix,
Pyerre, Jon Hohle, Thiago Mata (http://thiagomata.blog.com), lmeyrick
(https://sourceforge.net/projects/bcmath-js/this.), Linuxworld, duncan,
Gilbert, Sanjoy Roy, Shingo, sankai, Oskar Larsson H?gfeldt
(http://oskar-lh.name/), Denny Wardhana, 0m3r, Everlasto, Subhasis Deb,
josh, jd, Pier Paolo Ramon (http://www.mastersoup.com/), P, merabi, Soren
Hansen, Eugene Bulkin (http://doubleaw.com/), Der Simon
(http://innerdom.sourceforge.net/), echo is bad, Ozh, XoraX
(http://www.xorax.info), EdorFaus, JB, J A R, Marc Jansen, Francesco, LH,
Stoyan Kyosev (http://www.svest.org/), nord_ua, omid
(http://phpjs.org/functions/380:380#comment_137122), Brad Touesnard, MeEtc
(http://yass.meetcweb.com), Peter-Paul Koch
(http://www.quirksmode.org/js/beat.html), Olivier Louvignes
(http://mg-crea.com/), T0bsn, Tim Wiel, Bryan Elliott, Jalal Berrami,
Martin, JT, David Randall, Thomas Beaucourt (http://www.webapp.fr), taith,
vlado houba, Pierre-Luc Paour, Kristof Coomans (SCK-CEN Belgian Nucleair
Research Centre), Martin Pool, Kirk Strobeck, Rick Waldron, Brant Messenger
(http://www.brantmessenger.com/), Devan Penner-Woelk, Saulo Vallory, Wagner
B. Soares, Artur Tchernychev, Valentina De Rosa, Jason Wong
(http://carrot.org/), Christoph, Daniel Esteban, strftime, Mick@el, rezna,
Simon Willison (http://simonwillison.net), Anton Ongson, Gabriel Paderni,
Marco van Oort, penutbutterjelly, Philipp Lenssen, Bjorn Roesbeke
(http://www.bjornroesbeke.be/), Bug?, Eric Nagel, Tomasz Wesolowski,
Evertjan Garretsen, Bobby Drake, Blues (http://tech.bluesmoon.info/), Luke
Godfrey, Pul, uestla, Alan C, Ulrich, Rafal Kukawski, Yves Sucaet,
sowberry, Norman "zEh" Fuchs, hitwork, Zahlii, johnrembo, Nick Callen,
Steven Levithan (stevenlevithan.com), ejsanders, Scott Baker, Brian Tafoya
(http://www.premasolutions.com/), Philippe Jausions
(http://pear.php.net/user/jausions), Aidan Lister
(http://aidanlister.com/), Rob, e-mike, HKM, ChaosNo1, metjay, strcasecmp,
strcmp, Taras Bogach, jpfle, Alexander Ermolaev
(http://snippets.dzone.com/user/AlexanderErmolaev), DxGx, kilops, Orlando,
dptr1988, Le Torbi, James (http://www.james-bell.co.uk/), Pedro Tainha
(http://www.pedrotainha.com), James, Arnout Kazemier
(http://www.3rd-Eden.com), Chris McMacken, gabriel paderni, Yannoo,
FGFEmperor, baris ozdil, Tod Gentille, Greg Frazier, jakes, 3D-GRAF, Allan
Jensen (http://www.winternet.no), Howard Yeend, Benjamin Lupton, davook,
daniel airton wermann (http://wermann.com.br), Atli T¨®r, Maximusya, Ryan
W Tenney (http://ryan.10e.us), Alexander M Beedie, fearphage
(http://http/my.opera.com/fearphage/), Nathan Sepulveda, Victor, Matteo,
Billy, stensi, Cord, Manish, T.J. Leahy, Riddler
(http://www.frontierwebdev.com/), Rafa? Kukawski, FremyCompany, Matt
Bradley, Tim de Koning, Luis Salazar (http://www.freaky-media.com/), Diogo
Resende, Rival, Andrej Pavlovic, Garagoth, Le Torbi
(http://www.letorbi.de/), Dino, Josep Sanz (http://www.ws3.es/), rem,
Russell Walker (http://www.nbill.co.uk/), Jamie Beck
(http://www.terabit.ca/), setcookie, Michael, YUI Library:
http://developer.yahoo.com/yui/docs/YAHOO.util.DateLocale.html, Blues at
http://hacks.bluesmoon.info/strftime/strftime.js, Ben
(http://benblume.co.uk/), DtTvB
(http://dt.in.th/2008-09-16.string-length-in-bytes.html), Andreas, William,
meo, incidence, Cagri Ekin, Amirouche, Amir Habibi
(http://www.residence-mixte.com/), Luke Smith (http://lucassmith.name),
Kheang Hok Chin (http://www.distantia.ca/), Jay Klehr, Lorenzo Pisani,
Tony, Yen-Wei Liu, Greenseed, mk.keck, Leslie Hoare, dude, booeyOH, Ben
Bryan
Licensed under the MIT (MIT-LICENSE.txt) license.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL KEVIN VAN ZONNEVELD BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
/*global define*/
define('ThirdParty/sprintf',[],function() {
function sprintf () {
// http://kevin.vanzonneveld.net
// + original by: Ash Searle (http://hexmen.com/blog/)
// + namespaced by: Michael White (http://getsprink.com)
// + tweaked by: Jack
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Paulo Freitas
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Brett Zamir (http://brett-zamir.me)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Dj
// + improved by: Allidylls
// * example 1: sprintf("%01.2f", 123.1);
// * returns 1: 123.10
// * example 2: sprintf("[%10s]", 'monkey');
// * returns 2: '[ monkey]'
// * example 3: sprintf("[%'#10s]", 'monkey');
// * returns 3: '[####monkey]'
// * example 4: sprintf("%d", 123456789012345);
// * returns 4: '123456789012345'
var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g;
var a = arguments,
i = 0,
format = a[i++];
// pad()
var pad = function (str, len, chr, leftJustify) {
if (!chr) {
chr = ' ';
}
var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
return leftJustify ? str + padding : padding + str;
};
// justify()
var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
var diff = minWidth - value.length;
if (diff > 0) {
if (leftJustify || !zeroPad) {
value = pad(value, minWidth, customPadChar, leftJustify);
} else {
value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
}
}
return value;
};
// formatBaseX()
var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
// Note: casts negative numbers to positive ones
var number = value >>> 0;
prefix = prefix && number && {
'2': '0b',
'8': '0',
'16': '0x'
}[base] || '';
value = prefix + pad(number.toString(base), precision || 0, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad);
};
// formatString()
var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
if (precision != null) {
value = value.slice(0, precision);
}
return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
};
// doFormat()
var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) {
var number;
var prefix;
var method;
var textTransform;
var value;
if (substring == '%%') {
return '%';
}
// parse flags
var leftJustify = false,
positivePrefix = '',
zeroPad = false,
prefixBaseX = false,
customPadChar = ' ';
var flagsl = flags.length;
for (var j = 0; flags && j < flagsl; j++) {
switch (flags.charAt(j)) {
case ' ':
positivePrefix = ' ';
break;
case '+':
positivePrefix = '+';
break;
case '-':
leftJustify = true;
break;
case "'":
customPadChar = flags.charAt(j + 1);
break;
case '0':
zeroPad = true;
break;
case '#':
prefixBaseX = true;
break;
}
}
// parameters may be null, undefined, empty-string or real valued
// we want to ignore null, undefined and empty-string values
if (!minWidth) {
minWidth = 0;
} else if (minWidth == '*') {
minWidth = +a[i++];
} else if (minWidth.charAt(0) == '*') {
minWidth = +a[minWidth.slice(1, -1)];
} else {
minWidth = +minWidth;
}
// Note: undocumented perl feature:
if (minWidth < 0) {
minWidth = -minWidth;
leftJustify = true;
}
if (!isFinite(minWidth)) {
throw new Error('sprintf: (minimum-)width must be finite');
}
if (!precision) {
precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : undefined;
} else if (precision == '*') {
precision = +a[i++];
} else if (precision.charAt(0) == '*') {
precision = +a[precision.slice(1, -1)];
} else {
precision = +precision;
}
// grab value using valueIndex if required?
value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
switch (type) {
case 's':
return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
case 'c':
return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
case 'b':
return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'o':
return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'x':
return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'X':
return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
case 'u':
return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
case 'i':
case 'd':
number = +value || 0;
number = Math.round(number - number % 1); // Plain Math.round doesn't just truncate
prefix = number < 0 ? '-' : positivePrefix;
value = prefix + pad(String(Math.abs(number)), precision, '0', false);
return justify(value, prefix, leftJustify, minWidth, zeroPad);
case 'e':
case 'E':
case 'f': // Should handle locales (as per setlocale)
case 'F':
case 'g':
case 'G':
number = +value;
prefix = number < 0 ? '-' : positivePrefix;
method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
value = prefix + Math.abs(number)[method](precision);
return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
default:
return substring;
}
};
return format.replace(regex, doFormat);
}
return sprintf;
});
/*global define*/
define('Core/GregorianDate',[],function() {
'use strict';
/**
* Represents a Gregorian date in a more precise format than the JavaScript Date object.
* In addition to submillisecond precision, this object can also represent leap seconds.
* @alias GregorianDate
* @constructor
*
* @see JulianDate#toGregorianDate
*/
function GregorianDate(year, month, day, hour, minute, second, millisecond, isLeapSecond) {
/**
* Gets or sets the year as a whole number.
* @type {Number}
*/
this.year = year;
/**
* Gets or sets the month as a whole number with range [1, 12].
* @type {Number}
*/
this.month = month;
/**
* Gets or sets the day of the month as a whole number starting at 1.
* @type {Number}
*/
this.day = day;
/**
* Gets or sets the hour as a whole number with range [0, 23].
* @type {Number}
*/
this.hour = hour;
/**
* Gets or sets the minute of the hour as a whole number with range [0, 59].
* @type {Number}
*/
this.minute = minute;
/**
* Gets or sets the second of the minute as a whole number with range [0, 60], with 60 representing a leap second.
* @type {Number}
*/
this.second = second;
/**
* Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0).
* @type {Number}
*/
this.millisecond = millisecond;
/**
* Gets or sets whether this time is during a leap second.
* @type {Boolean}
*/
this.isLeapSecond = isLeapSecond;
}
return GregorianDate;
});
/*global define*/
define('Core/isLeapYear',[
'./DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* Determines if a given date is a leap year.
*
* @exports isLeapYear
*
* @param {Number} year The year to be tested.
* @returns {Boolean} True if year
is a leap year.
*
* @example
* var leapYear = Cesium.isLeapYear(2000); // true
*/
function isLeapYear(year) {
if (year === null || isNaN(year)) {
throw new DeveloperError('year is required and must be a number.');
}
return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
}
return isLeapYear;
});
/*global define*/
define('Core/LeapSecond',[],function() {
'use strict';
/**
* Describes a single leap second, which is constructed from a {@link JulianDate} and a
* numerical offset representing the number of seconds TAI is ahead of the UTC time standard.
* @alias LeapSecond
* @constructor
*
* @param {JulianDate} [date] A Julian date representing the time of the leap second.
* @param {Number} [offset] The cumulative number of seconds that TAI is ahead of UTC at the provided date.
*/
function LeapSecond(date, offset) {
/**
* Gets or sets the date at which this leap second occurs.
* @type {JulianDate}
*/
this.julianDate = date;
/**
* Gets or sets the cumulative number of seconds between the UTC and TAI time standards at the time
* of this leap second.
* @type {Number}
*/
this.offset = offset;
}
return LeapSecond;
});
/*global define*/
define('Core/TimeConstants',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Constants for time conversions like those done by {@link JulianDate}.
*
* @exports TimeConstants
*
* @see JulianDate
*
* @private
*/
var TimeConstants = {
/**
* The number of seconds in one millisecond: 0.001
* @type {Number}
* @constant
*/
SECONDS_PER_MILLISECOND : 0.001,
/**
* The number of seconds in one minute: 60
.
* @type {Number}
* @constant
*/
SECONDS_PER_MINUTE : 60.0,
/**
* The number of minutes in one hour: 60
.
* @type {Number}
* @constant
*/
MINUTES_PER_HOUR : 60.0,
/**
* The number of hours in one day: 24
.
* @type {Number}
* @constant
*/
HOURS_PER_DAY : 24.0,
/**
* The number of seconds in one hour: 3600
.
* @type {Number}
* @constant
*/
SECONDS_PER_HOUR : 3600.0,
/**
* The number of minutes in one day: 1440
.
* @type {Number}
* @constant
*/
MINUTES_PER_DAY : 1440.0,
/**
* The number of seconds in one day, ignoring leap seconds: 86400
.
* @type {Number}
* @constant
*/
SECONDS_PER_DAY : 86400.0,
/**
* The number of days in one Julian century: 36525
.
* @type {Number}
* @constant
*/
DAYS_PER_JULIAN_CENTURY : 36525.0,
/**
* One trillionth of a second.
* @type {Number}
* @constant
*/
PICOSECOND : 0.000000001,
/**
* The number of days to subtract from a Julian date to determine the
* modified Julian date, which gives the number of days since midnight
* on November 17, 1858.
* @type {Number}
* @constant
*/
MODIFIED_JULIAN_DATE_DIFFERENCE : 2400000.5
};
return freezeObject(TimeConstants);
});
/*global define*/
define('Core/TimeStandard',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Provides the type of time standards which JulianDate can take as input.
*
* @exports TimeStandard
*
* @see JulianDate
*/
var TimeStandard = {
/**
* Represents the coordinated Universal Time (UTC) time standard.
*
* UTC is related to TAI according to the relationship
* UTC = TAI - deltaT
where deltaT
is the number of leap
* seconds which have been introduced as of the time in TAI.
*
*/
UTC : 0,
/**
* Represents the International Atomic Time (TAI) time standard.
* TAI is the principal time standard to which the other time standards are related.
*/
TAI : 1
};
return freezeObject(TimeStandard);
});
/*global define*/
define('Core/JulianDate',[
'../ThirdParty/sprintf',
'./binarySearch',
'./defaultValue',
'./defined',
'./DeveloperError',
'./GregorianDate',
'./isLeapYear',
'./LeapSecond',
'./TimeConstants',
'./TimeStandard'
], function(
sprintf,
binarySearch,
defaultValue,
defined,
DeveloperError,
GregorianDate,
isLeapYear,
LeapSecond,
TimeConstants,
TimeStandard) {
'use strict';
var gregorianDateScratch = new GregorianDate();
var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
var daysInLeapFeburary = 29;
function compareLeapSecondDates(leapSecond, dateToFind) {
return JulianDate.compare(leapSecond.julianDate, dateToFind.julianDate);
}
// we don't really need a leap second instance, anything with a julianDate property will do
var binarySearchScratchLeapSecond = new LeapSecond();
function convertUtcToTai(julianDate) {
//Even though julianDate is in UTC, we'll treat it as TAI and
//search the leap second table for it.
binarySearchScratchLeapSecond.julianDate = julianDate;
var leapSeconds = JulianDate.leapSeconds;
var index = binarySearch(leapSeconds, binarySearchScratchLeapSecond, compareLeapSecondDates);
if (index < 0) {
index = ~index;
}
if (index >= leapSeconds.length) {
index = leapSeconds.length - 1;
}
var offset = leapSeconds[index].offset;
if (index > 0) {
//Now we have the index of the closest leap second that comes on or after our UTC time.
//However, if the difference between the UTC date being converted and the TAI
//defined leap second is greater than the offset, we are off by one and need to use
//the previous leap second.
var difference = JulianDate.secondsDifference(leapSeconds[index].julianDate, julianDate);
if (difference > offset) {
index--;
offset = leapSeconds[index].offset;
}
}
JulianDate.addSeconds(julianDate, offset, julianDate);
}
function convertTaiToUtc(julianDate, result) {
binarySearchScratchLeapSecond.julianDate = julianDate;
var leapSeconds = JulianDate.leapSeconds;
var index = binarySearch(leapSeconds, binarySearchScratchLeapSecond, compareLeapSecondDates);
if (index < 0) {
index = ~index;
}
//All times before our first leap second get the first offset.
if (index === 0) {
return JulianDate.addSeconds(julianDate, -leapSeconds[0].offset, result);
}
//All times after our leap second get the last offset.
if (index >= leapSeconds.length) {
return JulianDate.addSeconds(julianDate, -leapSeconds[index - 1].offset, result);
}
//Compute the difference between the found leap second and the time we are converting.
var difference = JulianDate.secondsDifference(leapSeconds[index].julianDate, julianDate);
if (difference === 0) {
//The date is in our leap second table.
return JulianDate.addSeconds(julianDate, -leapSeconds[index].offset, result);
}
if (difference <= 1.0) {
//The requested date is during the moment of a leap second, then we cannot convert to UTC
return undefined;
}
//The time is in between two leap seconds, index is the leap second after the date
//we're converting, so we subtract one to get the correct LeapSecond instance.
return JulianDate.addSeconds(julianDate, -leapSeconds[--index].offset, result);
}
function setComponents(wholeDays, secondsOfDay, julianDate) {
var extraDays = (secondsOfDay / TimeConstants.SECONDS_PER_DAY) | 0;
wholeDays += extraDays;
secondsOfDay -= TimeConstants.SECONDS_PER_DAY * extraDays;
if (secondsOfDay < 0) {
wholeDays--;
secondsOfDay += TimeConstants.SECONDS_PER_DAY;
}
julianDate.dayNumber = wholeDays;
julianDate.secondsOfDay = secondsOfDay;
return julianDate;
}
function computeJulianDateComponents(year, month, day, hour, minute, second, millisecond) {
// Algorithm from page 604 of the Explanatory Supplement to the
// Astronomical Almanac (Seidelmann 1992).
var a = ((month - 14) / 12) | 0;
var b = year + 4800 + a;
var dayNumber = (((1461 * b) / 4) | 0) + (((367 * (month - 2 - 12 * a)) / 12) | 0) - (((3 * (((b + 100) / 100) | 0)) / 4) | 0) + day - 32075;
// JulianDates are noon-based
hour = hour - 12;
if (hour < 0) {
hour += 24;
}
var secondsOfDay = second + ((hour * TimeConstants.SECONDS_PER_HOUR) + (minute * TimeConstants.SECONDS_PER_MINUTE) + (millisecond * TimeConstants.SECONDS_PER_MILLISECOND));
if (secondsOfDay >= 43200.0) {
dayNumber -= 1;
}
return [dayNumber, secondsOfDay];
}
//Regular expressions used for ISO8601 date parsing.
//YYYY
var matchCalendarYear = /^(\d{4})$/;
//YYYY-MM (YYYYMM is invalid)
var matchCalendarMonth = /^(\d{4})-(\d{2})$/;
//YYYY-DDD or YYYYDDD
var matchOrdinalDate = /^(\d{4})-?(\d{3})$/;
//YYYY-Www or YYYYWww or YYYY-Www-D or YYYYWwwD
var matchWeekDate = /^(\d{4})-?W(\d{2})-?(\d{1})?$/;
//YYYY-MM-DD or YYYYMMDD
var matchCalendarDate = /^(\d{4})-?(\d{2})-?(\d{2})$/;
// Match utc offset
var utcOffset = /([Z+\-])?(\d{2})?:?(\d{2})?$/;
// Match hours HH or HH.xxxxx
var matchHours = /^(\d{2})(\.\d+)?/.source + utcOffset.source;
// Match hours/minutes HH:MM HHMM.xxxxx
var matchHoursMinutes = /^(\d{2}):?(\d{2})(\.\d+)?/.source + utcOffset.source;
// Match hours/minutes HH:MM:SS HHMMSS.xxxxx
var matchHoursMinutesSeconds = /^(\d{2}):?(\d{2}):?(\d{2})(\.\d+)?/.source + utcOffset.source;
var iso8601ErrorMessage = 'Invalid ISO 8601 date.';
/**
* Represents an astronomical Julian date, which is the number of days since noon on January 1, -4712 (4713 BC).
* For increased precision, this class stores the whole number part of the date and the seconds
* part of the date in separate components. In order to be safe for arithmetic and represent
* leap seconds, the date is always stored in the International Atomic Time standard
* {@link TimeStandard.TAI}.
* @alias JulianDate
* @constructor
*
* @param {Number} [julianDayNumber=0.0] The Julian Day Number representing the number of whole days. Fractional days will also be handled correctly.
* @param {Number} [secondsOfDay=0.0] The number of seconds into the current Julian Day Number. Fractional seconds, negative seconds and seconds greater than a day will be handled correctly.
* @param {TimeStandard} [timeStandard=TimeStandard.UTC] The time standard in which the first two parameters are defined.
*/
function JulianDate(julianDayNumber, secondsOfDay, timeStandard) {
/**
* Gets or sets the number of whole days.
* @type {Number}
*/
this.dayNumber = undefined;
/**
* Gets or sets the number of seconds into the current day.
* @type {Number}
*/
this.secondsOfDay = undefined;
julianDayNumber = defaultValue(julianDayNumber, 0.0);
secondsOfDay = defaultValue(secondsOfDay, 0.0);
timeStandard = defaultValue(timeStandard, TimeStandard.UTC);
//If julianDayNumber is fractional, make it an integer and add the number of seconds the fraction represented.
var wholeDays = julianDayNumber | 0;
secondsOfDay = secondsOfDay + (julianDayNumber - wholeDays) * TimeConstants.SECONDS_PER_DAY;
setComponents(wholeDays, secondsOfDay, this);
if (timeStandard === TimeStandard.UTC) {
convertUtcToTai(this);
}
}
/**
* Creates a new instance from a JavaScript Date.
*
* @param {Date} date A JavaScript Date.
* @param {JulianDate} [result] An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter or a new instance if none was provided.
*
* @exception {DeveloperError} date must be a valid JavaScript Date.
*/
JulianDate.fromDate = function(date, result) {
if (!(date instanceof Date) || isNaN(date.getTime())) {
throw new DeveloperError('date must be a valid JavaScript Date.');
}
var components = computeJulianDateComponents(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
if (!defined(result)) {
return new JulianDate(components[0], components[1], TimeStandard.UTC);
}
setComponents(components[0], components[1], result);
convertUtcToTai(result);
return result;
};
/**
* Creates a new instance from a from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} date.
* This method is superior to Date.parse
because it will handle all valid formats defined by the ISO 8601
* specification, including leap seconds and sub-millisecond times, which discarded by most JavaScript implementations.
*
* @param {String} iso8601String An ISO 8601 date.
* @param {JulianDate} [result] An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter or a new instance if none was provided.
*
* @exception {DeveloperError} Invalid ISO 8601 date.
*/
JulianDate.fromIso8601 = function(iso8601String, result) {
if (typeof iso8601String !== 'string') {
throw new DeveloperError(iso8601ErrorMessage);
}
//Comma and decimal point both indicate a fractional number according to ISO 8601,
//start out by blanket replacing , with . which is the only valid such symbol in JS.
iso8601String = iso8601String.replace(',', '.');
//Split the string into its date and time components, denoted by a mandatory T
var tokens = iso8601String.split('T');
var year;
var month = 1;
var day = 1;
var hour = 0;
var minute = 0;
var second = 0;
var millisecond = 0;
//Lacking a time is okay, but a missing date is illegal.
var date = tokens[0];
var time = tokens[1];
var tmp;
var inLeapYear;
if (!defined(date)) {
throw new DeveloperError(iso8601ErrorMessage);
}
var dashCount;
//First match the date against possible regular expressions.
tokens = date.match(matchCalendarDate);
if (tokens !== null) {
dashCount = date.split('-').length - 1;
if (dashCount > 0 && dashCount !== 2) {
throw new DeveloperError(iso8601ErrorMessage);
}
year = +tokens[1];
month = +tokens[2];
day = +tokens[3];
} else {
tokens = date.match(matchCalendarMonth);
if (tokens !== null) {
year = +tokens[1];
month = +tokens[2];
} else {
tokens = date.match(matchCalendarYear);
if (tokens !== null) {
year = +tokens[1];
} else {
//Not a year/month/day so it must be an ordinal date.
var dayOfYear;
tokens = date.match(matchOrdinalDate);
if (tokens !== null) {
year = +tokens[1];
dayOfYear = +tokens[2];
inLeapYear = isLeapYear(year);
//This validation is only applicable for this format.
if (dayOfYear < 1 || (inLeapYear && dayOfYear > 366) || (!inLeapYear && dayOfYear > 365)) {
throw new DeveloperError(iso8601ErrorMessage);
}
} else {
tokens = date.match(matchWeekDate);
if (tokens !== null) {
//ISO week date to ordinal date from
//http://en.wikipedia.org/w/index.php?title=ISO_week_date&oldid=474176775
year = +tokens[1];
var weekNumber = +tokens[2];
var dayOfWeek = +tokens[3] || 0;
dashCount = date.split('-').length - 1;
if (dashCount > 0 &&
((!defined(tokens[3]) && dashCount !== 1) ||
(defined(tokens[3]) && dashCount !== 2))) {
throw new DeveloperError(iso8601ErrorMessage);
}
var january4 = new Date(Date.UTC(year, 0, 4));
dayOfYear = (weekNumber * 7) + dayOfWeek - january4.getUTCDay() - 3;
} else {
//None of our regular expressions succeeded in parsing the date properly.
throw new DeveloperError(iso8601ErrorMessage);
}
}
//Split an ordinal date into month/day.
tmp = new Date(Date.UTC(year, 0, 1));
tmp.setUTCDate(dayOfYear);
month = tmp.getUTCMonth() + 1;
day = tmp.getUTCDate();
}
}
}
//Now that we have all of the date components, validate them to make sure nothing is out of range.
inLeapYear = isLeapYear(year);
if (month < 1 || month > 12 || day < 1 || ((month !== 2 || !inLeapYear) && day > daysInMonth[month - 1]) || (inLeapYear && month === 2 && day > daysInLeapFeburary)) {
throw new DeveloperError(iso8601ErrorMessage);
}
//Not move onto the time string, which is much simpler.
var offsetIndex;
if (defined(time)) {
tokens = time.match(matchHoursMinutesSeconds);
if (tokens !== null) {
dashCount = time.split(':').length - 1;
if (dashCount > 0 && dashCount !== 2 && dashCount !== 3) {
throw new DeveloperError(iso8601ErrorMessage);
}
hour = +tokens[1];
minute = +tokens[2];
second = +tokens[3];
millisecond = +(tokens[4] || 0) * 1000.0;
offsetIndex = 5;
} else {
tokens = time.match(matchHoursMinutes);
if (tokens !== null) {
dashCount = time.split(':').length - 1;
if (dashCount > 2) {
throw new DeveloperError(iso8601ErrorMessage);
}
hour = +tokens[1];
minute = +tokens[2];
second = +(tokens[3] || 0) * 60.0;
offsetIndex = 4;
} else {
tokens = time.match(matchHours);
if (tokens !== null) {
hour = +tokens[1];
minute = +(tokens[2] || 0) * 60.0;
offsetIndex = 3;
} else {
throw new DeveloperError(iso8601ErrorMessage);
}
}
}
//Validate that all values are in proper range. Minutes and hours have special cases at 60 and 24.
if (minute >= 60 || second >= 61 || hour > 24 || (hour === 24 && (minute > 0 || second > 0 || millisecond > 0))) {
throw new DeveloperError(iso8601ErrorMessage);
}
//Check the UTC offset value, if no value exists, use local time
//a Z indicates UTC, + or - are offsets.
var offset = tokens[offsetIndex];
var offsetHours = +(tokens[offsetIndex + 1]);
var offsetMinutes = +(tokens[offsetIndex + 2] || 0);
switch (offset) {
case '+':
hour = hour - offsetHours;
minute = minute - offsetMinutes;
break;
case '-':
hour = hour + offsetHours;
minute = minute + offsetMinutes;
break;
case 'Z':
break;
default:
minute = minute + new Date(Date.UTC(year, month - 1, day, hour, minute)).getTimezoneOffset();
break;
}
} else {
//If no time is specified, it is considered the beginning of the day, local time.
minute = minute + new Date(year, month - 1, day).getTimezoneOffset();
}
//ISO8601 denotes a leap second by any time having a seconds component of 60 seconds.
//If that's the case, we need to temporarily subtract a second in order to build a UTC date.
//Then we add it back in after converting to TAI.
var isLeapSecond = second === 60;
if (isLeapSecond) {
second--;
}
//Even if we successfully parsed the string into its components, after applying UTC offset or
//special cases like 24:00:00 denoting midnight, we need to normalize the data appropriately.
//milliseconds can never be greater than 1000, and seconds can't be above 60, so we start with minutes
while (minute >= 60) {
minute -= 60;
hour++;
}
while (hour >= 24) {
hour -= 24;
day++;
}
tmp = (inLeapYear && month === 2) ? daysInLeapFeburary : daysInMonth[month - 1];
while (day > tmp) {
day -= tmp;
month++;
if (month > 12) {
month -= 12;
year++;
}
tmp = (inLeapYear && month === 2) ? daysInLeapFeburary : daysInMonth[month - 1];
}
//If UTC offset is at the beginning/end of the day, minutes can be negative.
while (minute < 0) {
minute += 60;
hour--;
}
while (hour < 0) {
hour += 24;
day--;
}
while (day < 1) {
month--;
if (month < 1) {
month += 12;
year--;
}
tmp = (inLeapYear && month === 2) ? daysInLeapFeburary : daysInMonth[month - 1];
day += tmp;
}
//Now create the JulianDate components from the Gregorian date and actually create our instance.
var components = computeJulianDateComponents(year, month, day, hour, minute, second, millisecond);
if (!defined(result)) {
result = new JulianDate(components[0], components[1], TimeStandard.UTC);
} else {
setComponents(components[0], components[1], result);
convertUtcToTai(result);
}
//If we were on a leap second, add it back.
if (isLeapSecond) {
JulianDate.addSeconds(result, 1, result);
}
return result;
};
/**
* Creates a new instance that represents the current system time.
* This is equivalent to calling JulianDate.fromDate(new Date());
.
*
* @param {JulianDate} [result] An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter or a new instance if none was provided.
*/
JulianDate.now = function(result) {
return JulianDate.fromDate(new Date(), result);
};
var toGregorianDateScratch = new JulianDate(0, 0, TimeStandard.TAI);
/**
* Creates a {@link GregorianDate} from the provided instance.
*
* @param {JulianDate} julianDate The date to be converted.
* @param {GregorianDate} [result] An existing instance to use for the result.
* @returns {GregorianDate} The modified result parameter or a new instance if none was provided.
*/
JulianDate.toGregorianDate = function(julianDate, result) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
var isLeapSecond = false;
var thisUtc = convertTaiToUtc(julianDate, toGregorianDateScratch);
if (!defined(thisUtc)) {
//Conversion to UTC will fail if we are during a leap second.
//If that's the case, subtract a second and convert again.
//JavaScript doesn't support leap seconds, so this results in second 59 being repeated twice.
JulianDate.addSeconds(julianDate, -1, toGregorianDateScratch);
thisUtc = convertTaiToUtc(toGregorianDateScratch, toGregorianDateScratch);
isLeapSecond = true;
}
var julianDayNumber = thisUtc.dayNumber;
var secondsOfDay = thisUtc.secondsOfDay;
if (secondsOfDay >= 43200.0) {
julianDayNumber += 1;
}
// Algorithm from page 604 of the Explanatory Supplement to the
// Astronomical Almanac (Seidelmann 1992).
var L = (julianDayNumber + 68569) | 0;
var N = (4 * L / 146097) | 0;
L = (L - (((146097 * N + 3) / 4) | 0)) | 0;
var I = ((4000 * (L + 1)) / 1461001) | 0;
L = (L - (((1461 * I) / 4) | 0) + 31) | 0;
var J = ((80 * L) / 2447) | 0;
var day = (L - (((2447 * J) / 80) | 0)) | 0;
L = (J / 11) | 0;
var month = (J + 2 - 12 * L) | 0;
var year = (100 * (N - 49) + I + L) | 0;
var hour = (secondsOfDay / TimeConstants.SECONDS_PER_HOUR) | 0;
var remainingSeconds = secondsOfDay - (hour * TimeConstants.SECONDS_PER_HOUR);
var minute = (remainingSeconds / TimeConstants.SECONDS_PER_MINUTE) | 0;
remainingSeconds = remainingSeconds - (minute * TimeConstants.SECONDS_PER_MINUTE);
var second = remainingSeconds | 0;
var millisecond = ((remainingSeconds - second) / TimeConstants.SECONDS_PER_MILLISECOND);
// JulianDates are noon-based
hour += 12;
if (hour > 23) {
hour -= 24;
}
//If we were on a leap second, add it back.
if (isLeapSecond) {
second += 1;
}
if (!defined(result)) {
return new GregorianDate(year, month, day, hour, minute, second, millisecond, isLeapSecond);
}
result.year = year;
result.month = month;
result.day = day;
result.hour = hour;
result.minute = minute;
result.second = second;
result.millisecond = millisecond;
result.isLeapSecond = isLeapSecond;
return result;
};
/**
* Creates a JavaScript Date from the provided instance.
* Since JavaScript dates are only accurate to the nearest millisecond and
* cannot represent a leap second, consider using {@link JulianDate.toGregorianDate} instead.
* If the provided JulianDate is during a leap second, the previous second is used.
*
* @param {JulianDate} julianDate The date to be converted.
* @returns {Date} A new instance representing the provided date.
*/
JulianDate.toDate = function(julianDate) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
var gDate = JulianDate.toGregorianDate(julianDate, gregorianDateScratch);
var second = gDate.second;
if (gDate.isLeapSecond) {
second -= 1;
}
return new Date(Date.UTC(gDate.year, gDate.month - 1, gDate.day, gDate.hour, gDate.minute, second, gDate.millisecond));
};
/**
* Creates an ISO8601 representation of the provided date.
*
* @param {JulianDate} julianDate The date to be converted.
* @param {Number} [precision] The number of fractional digits used to represent the seconds component. By default, the most precise representation is used.
* @returns {String} The ISO8601 representation of the provided date.
*/
JulianDate.toIso8601 = function(julianDate, precision) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
var gDate = JulianDate.toGregorianDate(julianDate, gDate);
var millisecondStr;
if (!defined(precision) && gDate.millisecond !== 0) {
//Forces milliseconds into a number with at least 3 digits to whatever the default toString() precision is.
millisecondStr = (gDate.millisecond * 0.01).toString().replace('.', '');
return sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%sZ", gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second, millisecondStr);
}
//Precision is either 0 or milliseconds is 0 with undefined precision, in either case, leave off milliseconds entirely
if (!defined(precision) || precision === 0) {
return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second);
}
//Forces milliseconds into a number with at least 3 digits to whatever the specified precision is.
millisecondStr = (gDate.millisecond * 0.01).toFixed(precision).replace('.', '').slice(0, precision);
return sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%sZ", gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second, millisecondStr);
};
/**
* Duplicates a JulianDate instance.
*
* @param {JulianDate} julianDate The date to duplicate.
* @param {JulianDate} [result] An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter or a new instance if none was provided. Returns undefined if julianDate is undefined.
*/
JulianDate.clone = function(julianDate, result) {
if (!defined(julianDate)) {
return undefined;
}
if (!defined(result)) {
return new JulianDate(julianDate.dayNumber, julianDate.secondsOfDay, TimeStandard.TAI);
}
result.dayNumber = julianDate.dayNumber;
result.secondsOfDay = julianDate.secondsOfDay;
return result;
};
/**
* Compares two instances.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Number} A negative value if left is less than right, a positive value if left is greater than right, or zero if left and right are equal.
*/
JulianDate.compare = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
var julianDayNumberDifference = left.dayNumber - right.dayNumber;
if (julianDayNumberDifference !== 0) {
return julianDayNumberDifference;
}
return left.secondsOfDay - right.secondsOfDay;
};
/**
* Compares two instances and returns true
if they are equal, false
otherwise.
*
* @param {JulianDate} [left] The first instance.
* @param {JulianDate} [right] The second instance.
* @returns {Boolean} true
if the dates are equal; otherwise, false
.
*/
JulianDate.equals = function(left, right) {
return (left === right) ||
(defined(left) &&
defined(right) &&
left.dayNumber === right.dayNumber &&
left.secondsOfDay === right.secondsOfDay);
};
/**
* Compares two instances and returns true
if they are within epsilon
seconds of
* each other. That is, in order for the dates to be considered equal (and for
* this function to return true
), the absolute value of the difference between them, in
* seconds, must be less than epsilon
.
*
* @param {JulianDate} [left] The first instance.
* @param {JulianDate} [right] The second instance.
* @param {Number} epsilon The maximum number of seconds that should separate the two instances.
* @returns {Boolean} true
if the two dates are within epsilon
seconds of each other; otherwise false
.
*/
JulianDate.equalsEpsilon = function(left, right, epsilon) {
if (!defined(epsilon)) {
throw new DeveloperError('epsilon is required.');
}
return (left === right) ||
(defined(left) &&
defined(right) &&
Math.abs(JulianDate.secondsDifference(left, right)) <= epsilon);
};
/**
* Computes the total number of whole and fractional days represented by the provided instance.
*
* @param {JulianDate} julianDate The date.
* @returns {Number} The Julian date as single floating point number.
*/
JulianDate.totalDays = function(julianDate) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
return julianDate.dayNumber + (julianDate.secondsOfDay / TimeConstants.SECONDS_PER_DAY);
};
/**
* Computes the difference in seconds between the provided instance.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Number} The difference, in seconds, when subtracting right
from left
.
*/
JulianDate.secondsDifference = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
var dayDifference = (left.dayNumber - right.dayNumber) * TimeConstants.SECONDS_PER_DAY;
return (dayDifference + (left.secondsOfDay - right.secondsOfDay));
};
/**
* Computes the difference in days between the provided instance.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Number} The difference, in days, when subtracting right
from left
.
*/
JulianDate.daysDifference = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
var dayDifference = (left.dayNumber - right.dayNumber);
var secondDifference = (left.secondsOfDay - right.secondsOfDay) / TimeConstants.SECONDS_PER_DAY;
return dayDifference + secondDifference;
};
/**
* Computes the number of seconds the provided instance is ahead of UTC.
*
* @param {JulianDate} julianDate The date.
* @returns {Number} The number of seconds the provided instance is ahead of UTC
*/
JulianDate.computeTaiMinusUtc = function(julianDate) {
binarySearchScratchLeapSecond.julianDate = julianDate;
var leapSeconds = JulianDate.leapSeconds;
var index = binarySearch(leapSeconds, binarySearchScratchLeapSecond, compareLeapSecondDates);
if (index < 0) {
index = ~index;
--index;
if (index < 0) {
index = 0;
}
}
return leapSeconds[index].offset;
};
/**
* Adds the provided number of seconds to the provided date instance.
*
* @param {JulianDate} julianDate The date.
* @param {Number} seconds The number of seconds to add or subtract.
* @param {JulianDate} result An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter.
*/
JulianDate.addSeconds = function(julianDate, seconds, result) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
if (!defined(seconds)) {
throw new DeveloperError('seconds is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
return setComponents(julianDate.dayNumber, julianDate.secondsOfDay + seconds, result);
};
/**
* Adds the provided number of minutes to the provided date instance.
*
* @param {JulianDate} julianDate The date.
* @param {Number} minutes The number of minutes to add or subtract.
* @param {JulianDate} result An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter.
*/
JulianDate.addMinutes = function(julianDate, minutes, result) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
if (!defined(minutes)) {
throw new DeveloperError('minutes is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var newSecondsOfDay = julianDate.secondsOfDay + (minutes * TimeConstants.SECONDS_PER_MINUTE);
return setComponents(julianDate.dayNumber, newSecondsOfDay, result);
};
/**
* Adds the provided number of hours to the provided date instance.
*
* @param {JulianDate} julianDate The date.
* @param {Number} hours The number of hours to add or subtract.
* @param {JulianDate} result An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter.
*/
JulianDate.addHours = function(julianDate, hours, result) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
if (!defined(hours)) {
throw new DeveloperError('hours is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var newSecondsOfDay = julianDate.secondsOfDay + (hours * TimeConstants.SECONDS_PER_HOUR);
return setComponents(julianDate.dayNumber, newSecondsOfDay, result);
};
/**
* Adds the provided number of days to the provided date instance.
*
* @param {JulianDate} julianDate The date.
* @param {Number} days The number of days to add or subtract.
* @param {JulianDate} result An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter.
*/
JulianDate.addDays = function(julianDate, days, result) {
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
if (!defined(days)) {
throw new DeveloperError('days is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var newJulianDayNumber = julianDate.dayNumber + days;
return setComponents(newJulianDayNumber, julianDate.secondsOfDay, result);
};
/**
* Compares the provided instances and returns true
if left
is earlier than right
, false
otherwise.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Boolean} true
if left
is earlier than right
, false
otherwise.
*/
JulianDate.lessThan = function(left, right) {
return JulianDate.compare(left, right) < 0;
};
/**
* Compares the provided instances and returns true
if left
is earlier than or equal to right
, false
otherwise.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Boolean} true
if left
is earlier than or equal to right
, false
otherwise.
*/
JulianDate.lessThanOrEquals = function(left, right) {
return JulianDate.compare(left, right) <= 0;
};
/**
* Compares the provided instances and returns true
if left
is later than right
, false
otherwise.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Boolean} true
if left
is later than right
, false
otherwise.
*/
JulianDate.greaterThan = function(left, right) {
return JulianDate.compare(left, right) > 0;
};
/**
* Compares the provided instances and returns true
if left
is later than or equal to right
, false
otherwise.
*
* @param {JulianDate} left The first instance.
* @param {JulianDate} right The second instance.
* @returns {Boolean} true
if left
is later than or equal to right
, false
otherwise.
*/
JulianDate.greaterThanOrEquals = function(left, right) {
return JulianDate.compare(left, right) >= 0;
};
/**
* Duplicates this instance.
*
* @param {JulianDate} [result] An existing instance to use for the result.
* @returns {JulianDate} The modified result parameter or a new instance if none was provided.
*/
JulianDate.prototype.clone = function(result) {
return JulianDate.clone(this, result);
};
/**
* Compares this and the provided instance and returns true
if they are equal, false
otherwise.
*
* @param {JulianDate} [right] The second instance.
* @returns {Boolean} true
if the dates are equal; otherwise, false
.
*/
JulianDate.prototype.equals = function(right) {
return JulianDate.equals(this, right);
};
/**
* Compares this and the provided instance and returns true
if they are within epsilon
seconds of
* each other. That is, in order for the dates to be considered equal (and for
* this function to return true
), the absolute value of the difference between them, in
* seconds, must be less than epsilon
.
*
* @param {JulianDate} [right] The second instance.
* @param {Number} epsilon The maximum number of seconds that should separate the two instances.
* @returns {Boolean} true
if the two dates are within epsilon
seconds of each other; otherwise false
.
*/
JulianDate.prototype.equalsEpsilon = function(right, epsilon) {
return JulianDate.equalsEpsilon(this, right, epsilon);
};
/**
* Creates a string representing this date in ISO8601 format.
*
* @returns {String} A string representing this date in ISO8601 format.
*/
JulianDate.prototype.toString = function() {
return JulianDate.toIso8601(this);
};
/**
* Gets or sets the list of leap seconds used throughout Cesium.
* @memberof JulianDate
* @type {LeapSecond[]}
*/
JulianDate.leapSeconds = [
new LeapSecond(new JulianDate(2441317, 43210.0, TimeStandard.TAI), 10), // January 1, 1972 00:00:00 UTC
new LeapSecond(new JulianDate(2441499, 43211.0, TimeStandard.TAI), 11), // July 1, 1972 00:00:00 UTC
new LeapSecond(new JulianDate(2441683, 43212.0, TimeStandard.TAI), 12), // January 1, 1973 00:00:00 UTC
new LeapSecond(new JulianDate(2442048, 43213.0, TimeStandard.TAI), 13), // January 1, 1974 00:00:00 UTC
new LeapSecond(new JulianDate(2442413, 43214.0, TimeStandard.TAI), 14), // January 1, 1975 00:00:00 UTC
new LeapSecond(new JulianDate(2442778, 43215.0, TimeStandard.TAI), 15), // January 1, 1976 00:00:00 UTC
new LeapSecond(new JulianDate(2443144, 43216.0, TimeStandard.TAI), 16), // January 1, 1977 00:00:00 UTC
new LeapSecond(new JulianDate(2443509, 43217.0, TimeStandard.TAI), 17), // January 1, 1978 00:00:00 UTC
new LeapSecond(new JulianDate(2443874, 43218.0, TimeStandard.TAI), 18), // January 1, 1979 00:00:00 UTC
new LeapSecond(new JulianDate(2444239, 43219.0, TimeStandard.TAI), 19), // January 1, 1980 00:00:00 UTC
new LeapSecond(new JulianDate(2444786, 43220.0, TimeStandard.TAI), 20), // July 1, 1981 00:00:00 UTC
new LeapSecond(new JulianDate(2445151, 43221.0, TimeStandard.TAI), 21), // July 1, 1982 00:00:00 UTC
new LeapSecond(new JulianDate(2445516, 43222.0, TimeStandard.TAI), 22), // July 1, 1983 00:00:00 UTC
new LeapSecond(new JulianDate(2446247, 43223.0, TimeStandard.TAI), 23), // July 1, 1985 00:00:00 UTC
new LeapSecond(new JulianDate(2447161, 43224.0, TimeStandard.TAI), 24), // January 1, 1988 00:00:00 UTC
new LeapSecond(new JulianDate(2447892, 43225.0, TimeStandard.TAI), 25), // January 1, 1990 00:00:00 UTC
new LeapSecond(new JulianDate(2448257, 43226.0, TimeStandard.TAI), 26), // January 1, 1991 00:00:00 UTC
new LeapSecond(new JulianDate(2448804, 43227.0, TimeStandard.TAI), 27), // July 1, 1992 00:00:00 UTC
new LeapSecond(new JulianDate(2449169, 43228.0, TimeStandard.TAI), 28), // July 1, 1993 00:00:00 UTC
new LeapSecond(new JulianDate(2449534, 43229.0, TimeStandard.TAI), 29), // July 1, 1994 00:00:00 UTC
new LeapSecond(new JulianDate(2450083, 43230.0, TimeStandard.TAI), 30), // January 1, 1996 00:00:00 UTC
new LeapSecond(new JulianDate(2450630, 43231.0, TimeStandard.TAI), 31), // July 1, 1997 00:00:00 UTC
new LeapSecond(new JulianDate(2451179, 43232.0, TimeStandard.TAI), 32), // January 1, 1999 00:00:00 UTC
new LeapSecond(new JulianDate(2453736, 43233.0, TimeStandard.TAI), 33), // January 1, 2006 00:00:00 UTC
new LeapSecond(new JulianDate(2454832, 43234.0, TimeStandard.TAI), 34), // January 1, 2009 00:00:00 UTC
new LeapSecond(new JulianDate(2456109, 43235.0, TimeStandard.TAI), 35), // July 1, 2012 00:00:00 UTC
new LeapSecond(new JulianDate(2457204, 43236.0, TimeStandard.TAI), 36), // July 1, 2015 00:00:00 UTC
new LeapSecond(new JulianDate(2457754, 43237.0, TimeStandard.TAI), 37) // January 1, 2017 00:00:00 UTC
];
return JulianDate;
});
/*global define*/
define('Core/clone',[
'./defaultValue'
], function(
defaultValue) {
'use strict';
/**
* Clones an object, returning a new object containing the same properties.
*
* @exports clone
*
* @param {Object} object The object to clone.
* @param {Boolean} [deep=false] If true, all properties will be deep cloned recursively.
* @returns {Object} The cloned object.
*/
function clone(object, deep) {
if (object === null || typeof object !== 'object') {
return object;
}
deep = defaultValue(deep, false);
var result = new object.constructor();
for ( var propertyName in object) {
if (object.hasOwnProperty(propertyName)) {
var value = object[propertyName];
if (deep) {
value = clone(value, deep);
}
result[propertyName] = value;
}
}
return result;
}
return clone;
});
/*global define*/
define('Core/parseResponseHeaders',[], function() {
'use strict';
/**
* Parses the result of XMLHttpRequest's getAllResponseHeaders() method into
* a dictionary.
*
* @exports parseResponseHeaders
*
* @param {String} headerString The header string returned by getAllResponseHeaders(). The format is
* described here: http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
* @returns {Object} A dictionary of key/value pairs, where each key is the name of a header and the corresponding value
* is that header's value.
*
* @private
*/
function parseResponseHeaders(headerString) {
var headers = {};
if (!headerString) {
return headers;
}
var headerPairs = headerString.split('\u000d\u000a');
for (var i = 0; i < headerPairs.length; ++i) {
var headerPair = headerPairs[i];
// Can't use split() here because it does the wrong thing
// if the header value has the string ": " in it.
var index = headerPair.indexOf('\u003a\u0020');
if (index > 0) {
var key = headerPair.substring(0, index);
var val = headerPair.substring(index + 2);
headers[key] = val;
}
}
return headers;
}
return parseResponseHeaders;
});
/*global define*/
define('Core/RequestErrorEvent',[
'./defined',
'./parseResponseHeaders'
], function(
defined,
parseResponseHeaders) {
'use strict';
/**
* An event that is raised when a request encounters an error.
*
* @constructor
* @alias RequestErrorEvent
*
* @param {Number} [statusCode] The HTTP error status code, such as 404.
* @param {Object} [response] The response included along with the error.
* @param {String|Object} [responseHeaders] The response headers, represented either as an object literal or as a
* string in the format returned by XMLHttpRequest's getAllResponseHeaders() function.
*/
function RequestErrorEvent(statusCode, response, responseHeaders) {
/**
* The HTTP error status code, such as 404. If the error does not have a particular
* HTTP code, this property will be undefined.
*
* @type {Number}
*/
this.statusCode = statusCode;
/**
* The response included along with the error. If the error does not include a response,
* this property will be undefined.
*
* @type {Object}
*/
this.response = response;
/**
* The headers included in the response, represented as an object literal of key/value pairs.
* If the error does not include any headers, this property will be undefined.
*
* @type {Object}
*/
this.responseHeaders = responseHeaders;
if (typeof this.responseHeaders === 'string') {
this.responseHeaders = parseResponseHeaders(this.responseHeaders);
}
}
/**
* Creates a string representing this RequestErrorEvent.
* @memberof RequestErrorEvent
*
* @returns {String} A string representing the provided RequestErrorEvent.
*/
RequestErrorEvent.prototype.toString = function() {
var str = 'Request has failed.';
if (defined(this.statusCode)) {
str += ' Status Code: ' + this.statusCode;
}
return str;
};
return RequestErrorEvent;
});
/**
* @license
*
* Grauw URI utilities
*
* See: http://hg.grauw.nl/grauw-lib/file/tip/src/uri.js
*
* @author Laurens Holst (http://www.grauw.nl/)
*
* Copyright 2012 Laurens Holst
*
* 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.
*
*/
/*global define*/
define('ThirdParty/Uri',[],function() {
/**
* Constructs a URI object.
* @constructor
* @class Implementation of URI parsing and base URI resolving algorithm in RFC 3986.
* @param {string|URI} uri A string or URI object to create the object from.
*/
function URI(uri) {
if (uri instanceof URI) { // copy constructor
this.scheme = uri.scheme;
this.authority = uri.authority;
this.path = uri.path;
this.query = uri.query;
this.fragment = uri.fragment;
} else if (uri) { // uri is URI string or cast to string
var c = parseRegex.exec(uri);
this.scheme = c[1];
this.authority = c[2];
this.path = c[3];
this.query = c[4];
this.fragment = c[5];
}
}
// Initial values on the prototype
URI.prototype.scheme = null;
URI.prototype.authority = null;
URI.prototype.path = '';
URI.prototype.query = null;
URI.prototype.fragment = null;
// Regular expression from RFC 3986 appendix B
var parseRegex = new RegExp('^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?$');
/**
* Returns the scheme part of the URI.
* In "http://example.com:80/a/b?x#y" this is "http".
*/
URI.prototype.getScheme = function() {
return this.scheme;
};
/**
* Returns the authority part of the URI.
* In "http://example.com:80/a/b?x#y" this is "example.com:80".
*/
URI.prototype.getAuthority = function() {
return this.authority;
};
/**
* Returns the path part of the URI.
* In "http://example.com:80/a/b?x#y" this is "/a/b".
* In "mailto:mike@example.com" this is "mike@example.com".
*/
URI.prototype.getPath = function() {
return this.path;
};
/**
* Returns the query part of the URI.
* In "http://example.com:80/a/b?x#y" this is "x".
*/
URI.prototype.getQuery = function() {
return this.query;
};
/**
* Returns the fragment part of the URI.
* In "http://example.com:80/a/b?x#y" this is "y".
*/
URI.prototype.getFragment = function() {
return this.fragment;
};
/**
* Tests whether the URI is an absolute URI.
* See RFC 3986 section 4.3.
*/
URI.prototype.isAbsolute = function() {
return !!this.scheme && !this.fragment;
};
///**
//* Extensive validation of the URI against the ABNF in RFC 3986
//*/
//URI.prototype.validate
/**
* Tests whether the URI is a same-document reference.
* See RFC 3986 section 4.4.
*
* To perform more thorough comparison, you can normalise the URI objects.
*/
URI.prototype.isSameDocumentAs = function(uri) {
return uri.scheme == this.scheme &&
uri.authority == this.authority &&
uri.path == this.path &&
uri.query == this.query;
};
/**
* Simple String Comparison of two URIs.
* See RFC 3986 section 6.2.1.
*
* To perform more thorough comparison, you can normalise the URI objects.
*/
URI.prototype.equals = function(uri) {
return this.isSameDocumentAs(uri) && uri.fragment == this.fragment;
};
/**
* Normalizes the URI using syntax-based normalization.
* This includes case normalization, percent-encoding normalization and path segment normalization.
* XXX: Percent-encoding normalization does not escape characters that need to be escaped.
* (Although that would not be a valid URI in the first place. See validate().)
* See RFC 3986 section 6.2.2.
*/
URI.prototype.normalize = function() {
this.removeDotSegments();
if (this.scheme)
this.scheme = this.scheme.toLowerCase();
if (this.authority)
this.authority = this.authority.replace(authorityRegex, replaceAuthority).
replace(caseRegex, replaceCase);
if (this.path)
this.path = this.path.replace(caseRegex, replaceCase);
if (this.query)
this.query = this.query.replace(caseRegex, replaceCase);
if (this.fragment)
this.fragment = this.fragment.replace(caseRegex, replaceCase);
};
var caseRegex = /%[0-9a-z]{2}/gi;
var percentRegex = /[a-zA-Z0-9\-\._~]/;
var authorityRegex = /(.*@)?([^@:]*)(:.*)?/;
function replaceCase(str) {
var dec = unescape(str);
return percentRegex.test(dec) ? dec : str.toUpperCase();
}
function replaceAuthority(str, p1, p2, p3) {
return (p1 || '') + p2.toLowerCase() + (p3 || '');
}
/**
* Resolve a relative URI (this) against a base URI.
* The base URI must be an absolute URI.
* See RFC 3986 section 5.2
*/
URI.prototype.resolve = function(baseURI) {
var uri = new URI();
if (this.scheme) {
uri.scheme = this.scheme;
uri.authority = this.authority;
uri.path = this.path;
uri.query = this.query;
} else {
uri.scheme = baseURI.scheme;
if (this.authority) {
uri.authority = this.authority;
uri.path = this.path;
uri.query = this.query;
} else {
uri.authority = baseURI.authority;
if (this.path == '') {
uri.path = baseURI.path;
uri.query = this.query || baseURI.query;
} else {
if (this.path.charAt(0) == '/') {
uri.path = this.path;
uri.removeDotSegments();
} else {
if (baseURI.authority && baseURI.path == '') {
uri.path = '/' + this.path;
} else {
uri.path = baseURI.path.substring(0, baseURI.path.lastIndexOf('/') + 1) + this.path;
}
uri.removeDotSegments();
}
uri.query = this.query;
}
}
}
uri.fragment = this.fragment;
return uri;
};
/**
* Remove dot segments from path.
* See RFC 3986 section 5.2.4
* @private
*/
URI.prototype.removeDotSegments = function() {
var input = this.path.split('/'),
output = [],
segment,
absPath = input[0] == '';
if (absPath)
input.shift();
var sFirst = input[0] == '' ? input.shift() : null;
while (input.length) {
segment = input.shift();
if (segment == '..') {
output.pop();
} else if (segment != '.') {
output.push(segment);
}
}
if (segment == '.' || segment == '..')
output.push('');
if (absPath)
output.unshift('');
this.path = output.join('/');
};
// We don't like this function because it builds up a cache that is never cleared.
// /**
// * Resolves a relative URI against an absolute base URI.
// * Convenience method.
// * @param {String} uri the relative URI to resolve
// * @param {String} baseURI the base URI (must be absolute) to resolve against
// */
// URI.resolve = function(sURI, sBaseURI) {
// var uri = cache[sURI] || (cache[sURI] = new URI(sURI));
// var baseURI = cache[sBaseURI] || (cache[sBaseURI] = new URI(sBaseURI));
// return uri.resolve(baseURI).toString();
// };
// var cache = {};
/**
* Serialises the URI to a string.
*/
URI.prototype.toString = function() {
var result = '';
if (this.scheme)
result += this.scheme + ':';
if (this.authority)
result += '//' + this.authority;
result += this.path;
if (this.query)
result += '?' + this.query;
if (this.fragment)
result += '#' + this.fragment;
return result;
};
return URI;
});
/*global define*/
define('Core/TrustedServers',[
'../ThirdParty/Uri',
'./defined',
'./DeveloperError'
], function(
Uri,
defined,
DeveloperError) {
'use strict';
/**
* A singleton that contains all of the servers that are trusted. Credentials will be sent with
* any requests to these servers.
*
* @exports TrustedServers
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
*/
var TrustedServers = {};
var _servers = {};
/**
* Adds a trusted server to the registry
*
* @param {String} host The host to be added.
* @param {Number} port The port used to access the host.
*
* @example
* // Add a trusted server
* TrustedServers.add('my.server.com', 80);
*/
TrustedServers.add = function(host, port) {
if (!defined(host)) {
throw new DeveloperError('host is required.');
}
if (!defined(port) || port <= 0) {
throw new DeveloperError('port is required to be greater than 0.');
}
var authority = host.toLowerCase() + ':' + port;
if (!defined(_servers[authority])) {
_servers[authority] = true;
}
};
/**
* Removes a trusted server from the registry
*
* @param {String} host The host to be removed.
* @param {Number} port The port used to access the host.
*
* @example
* // Remove a trusted server
* TrustedServers.remove('my.server.com', 80);
*/
TrustedServers.remove = function(host, port) {
if (!defined(host)) {
throw new DeveloperError('host is required.');
}
if (!defined(port) || port <= 0) {
throw new DeveloperError('port is required to be greater than 0.');
}
var authority = host.toLowerCase() + ':' + port;
if (defined(_servers[authority])) {
delete _servers[authority];
}
};
function getAuthority(url) {
var uri = new Uri(url);
uri.normalize();
// Removes username:password@ so we just have host[:port]
var authority = uri.getAuthority();
if (!defined(authority)) {
return undefined; // Relative URL
}
if (authority.indexOf('@') !== -1) {
var parts = authority.split('@');
authority = parts[1];
}
// If the port is missing add one based on the scheme
if (authority.indexOf(':') === -1) {
var scheme = uri.getScheme();
if (!defined(scheme)) {
scheme = window.location.protocol;
scheme = scheme.substring(0, scheme.length-1);
}
if (scheme === 'http') {
authority += ':80';
} else if (scheme === 'https') {
authority += ':443';
} else {
return undefined;
}
}
return authority;
}
/**
* Tests whether a server is trusted or not. The server must have been added with the port if it is included in the url.
*
* @param {String} url The url to be tested against the trusted list
*
* @returns {boolean} Returns true if url is trusted, false otherwise.
*
* @example
* // Add server
* TrustedServers.add('my.server.com', 81);
*
* // Check if server is trusted
* if (TrustedServers.contains('https://my.server.com:81/path/to/file.png')) {
* // my.server.com:81 is trusted
* }
* if (TrustedServers.contains('https://my.server.com/path/to/file.png')) {
* // my.server.com isn't trusted
* }
*/
TrustedServers.contains = function(url) {
if (!defined(url)) {
throw new DeveloperError('url is required.');
}
var authority = getAuthority(url);
if (defined(authority) && defined(_servers[authority])) {
return true;
}
return false;
};
/**
* Clears the registry
*
* @example
* // Remove a trusted server
* TrustedServers.clear();
*/
TrustedServers.clear = function() {
_servers = {};
};
return TrustedServers;
});
/*global define*/
define('Core/loadWithXhr',[
'../ThirdParty/when',
'./defaultValue',
'./defined',
'./DeveloperError',
'./RequestErrorEvent',
'./RuntimeError',
'./TrustedServers'
], function(
when,
defaultValue,
defined,
DeveloperError,
RequestErrorEvent,
RuntimeError,
TrustedServers) {
'use strict';
/**
* Asynchronously loads the given URL. Returns a promise that will resolve to
* the result once loaded, or reject if the URL failed to load. The data is loaded
* using XMLHttpRequest, which means that in order to make requests to another origin,
* the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
*
* @exports loadWithXhr
*
* @param {Object} options Object with the following properties:
* @param {String|Promise.} options.url The URL of the data, or a promise for the URL.
* @param {String} [options.responseType] The type of response. This controls the type of item returned.
* @param {String} [options.method='GET'] The HTTP method to use.
* @param {String} [options.data] The data to send with the request, if any.
* @param {Object} [options.headers] HTTP headers to send with the request, if any.
* @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // Load a single URL asynchronously. In real code, you should use loadBlob instead.
* Cesium.loadWithXhr({
* url : 'some/url',
* responseType : 'blob'
* }).then(function(blob) {
* // use the data
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see loadArrayBuffer
* @see loadBlob
* @see loadJson
* @see loadText
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadWithXhr(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
var responseType = options.responseType;
var method = defaultValue(options.method, 'GET');
var data = options.data;
var headers = options.headers;
var overrideMimeType = options.overrideMimeType;
return when(options.url, function(url) {
var deferred = when.defer();
loadWithXhr.load(url, responseType, method, data, headers, deferred, overrideMimeType);
return deferred.promise;
});
}
var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
function decodeDataUriText(isBase64, data) {
var result = decodeURIComponent(data);
if (isBase64) {
return atob(result);
}
return result;
}
function decodeDataUriArrayBuffer(isBase64, data) {
var byteString = decodeDataUriText(isBase64, data);
var buffer = new ArrayBuffer(byteString.length);
var view = new Uint8Array(buffer);
for (var i = 0; i < byteString.length; i++) {
view[i] = byteString.charCodeAt(i);
}
return buffer;
}
function decodeDataUri(dataUriRegexResult, responseType) {
responseType = defaultValue(responseType, '');
var mimeType = dataUriRegexResult[1];
var isBase64 = !!dataUriRegexResult[2];
var data = dataUriRegexResult[3];
switch (responseType) {
case '':
case 'text':
return decodeDataUriText(isBase64, data);
case 'arraybuffer':
return decodeDataUriArrayBuffer(isBase64, data);
case 'blob':
var buffer = decodeDataUriArrayBuffer(isBase64, data);
return new Blob([buffer], {
type : mimeType
});
case 'document':
var parser = new DOMParser();
return parser.parseFromString(decodeDataUriText(isBase64, data), mimeType);
case 'json':
return JSON.parse(decodeDataUriText(isBase64, data));
default:
throw new DeveloperError('Unhandled responseType: ' + responseType);
}
}
// This is broken out into a separate function so that it can be mocked for testing purposes.
loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) {
var dataUriRegexResult = dataUriRegex.exec(url);
if (dataUriRegexResult !== null) {
deferred.resolve(decodeDataUri(dataUriRegexResult, responseType));
return;
}
var xhr = new XMLHttpRequest();
if (TrustedServers.contains(url)) {
xhr.withCredentials = true;
}
if (defined(overrideMimeType) && defined(xhr.overrideMimeType)) {
xhr.overrideMimeType(overrideMimeType);
}
xhr.open(method, url, true);
if (defined(headers)) {
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, headers[key]);
}
}
}
if (defined(responseType)) {
xhr.responseType = responseType;
}
xhr.onload = function() {
if (xhr.status < 200 || xhr.status >= 300) {
deferred.reject(new RequestErrorEvent(xhr.status, xhr.response, xhr.getAllResponseHeaders()));
return;
}
var response = xhr.response;
var browserResponseType = xhr.responseType;
//All modern browsers will go into either the first if block or last else block.
//Other code paths support older browsers that either do not support the supplied responseType
//or do not support the xhr.response property.
if (defined(response) && (!defined(responseType) || (browserResponseType === responseType))) {
deferred.resolve(response);
} else if ((responseType === 'json') && typeof response === 'string') {
try {
deferred.resolve(JSON.parse(response));
} catch (e) {
deferred.reject(e);
}
} else if ((browserResponseType === '' || browserResponseType === 'document') && defined(xhr.responseXML) && xhr.responseXML.hasChildNodes()) {
deferred.resolve(xhr.responseXML);
} else if ((browserResponseType === '' || browserResponseType === 'text') && defined(xhr.responseText)) {
deferred.resolve(xhr.responseText);
} else {
deferred.reject(new RuntimeError('Invalid XMLHttpRequest response type.'));
}
};
xhr.onerror = function(e) {
deferred.reject(new RequestErrorEvent());
};
xhr.send(data);
};
loadWithXhr.defaultLoad = loadWithXhr.load;
return loadWithXhr;
});
/*global define*/
define('Core/loadText',[
'./loadWithXhr'
], function(
loadWithXhr) {
'use strict';
/**
* Asynchronously loads the given URL as text. Returns a promise that will resolve to
* a String once loaded, or reject if the URL failed to load. The data is loaded
* using XMLHttpRequest, which means that in order to make requests to another origin,
* the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
*
* @exports loadText
*
* @param {String|Promise.} url The URL to request, or a promise for the URL.
* @param {Object} [headers] HTTP headers to send with the request.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load text from a URL, setting a custom header
* Cesium.loadText('http://someUrl.com/someJson.txt', {
* 'X-Custom-Header' : 'some value'
* }).then(function(text) {
* // Do something with the text
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadText(url, headers) {
return loadWithXhr({
url : url,
headers : headers
});
}
return loadText;
});
/*global define*/
define('Core/loadJson',[
'./clone',
'./defined',
'./DeveloperError',
'./loadText'
], function(
clone,
defined,
DeveloperError,
loadText) {
'use strict';
var defaultHeaders = {
Accept : 'application/json,*/*;q=0.01'
};
// note: */* below is */* but that ends the comment block early
/**
* Asynchronously loads the given URL as JSON. Returns a promise that will resolve to
* a JSON object once loaded, or reject if the URL failed to load. The data is loaded
* using XMLHttpRequest, which means that in order to make requests to another origin,
* the server must have Cross-Origin Resource Sharing (CORS) headers enabled. This function
* adds 'Accept: application/json,*/*;q=0.01' to the request headers, if not
* already specified.
*
* @exports loadJson
*
* @param {String|Promise.} url The URL to request, or a promise for the URL.
* @param {Object} [headers] HTTP headers to send with the request.
* 'Accept: application/json,*/*;q=0.01' is added to the request headers automatically
* if not specified.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* Cesium.loadJson('http://someUrl.com/someJson.txt').then(function(jsonData) {
* // Do something with the JSON object
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see loadText
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadJson(url, headers) {
if (!defined(url)) {
throw new DeveloperError('url is required.');
}
if (!defined(headers)) {
headers = defaultHeaders;
} else if (!defined(headers.Accept)) {
// clone before adding the Accept header
headers = clone(headers);
headers.Accept = defaultHeaders.Accept;
}
return loadText(url, headers).then(function(value) {
return JSON.parse(value);
});
}
return loadJson;
});
/*global define*/
define('Core/EarthOrientationParameters',[
'../ThirdParty/when',
'./binarySearch',
'./defaultValue',
'./defined',
'./EarthOrientationParametersSample',
'./freezeObject',
'./JulianDate',
'./LeapSecond',
'./loadJson',
'./RuntimeError',
'./TimeConstants',
'./TimeStandard'
], function(
when,
binarySearch,
defaultValue,
defined,
EarthOrientationParametersSample,
freezeObject,
JulianDate,
LeapSecond,
loadJson,
RuntimeError,
TimeConstants,
TimeStandard) {
'use strict';
/**
* Specifies Earth polar motion coordinates and the difference between UT1 and UTC.
* These Earth Orientation Parameters (EOP) are primarily used in the transformation from
* the International Celestial Reference Frame (ICRF) to the International Terrestrial
* Reference Frame (ITRF).
*
* @alias EarthOrientationParameters
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {String} [options.url] The URL from which to obtain EOP data. If neither this
* parameter nor options.data is specified, all EOP values are assumed
* to be 0.0. If options.data is specified, this parameter is
* ignored.
* @param {Object} [options.data] The actual EOP data. If neither this
* parameter nor options.data is specified, all EOP values are assumed
* to be 0.0.
* @param {Boolean} [options.addNewLeapSeconds=true] True if leap seconds that
* are specified in the EOP data but not in {@link JulianDate.leapSeconds}
* should be added to {@link JulianDate.leapSeconds}. False if
* new leap seconds should be handled correctly in the context
* of the EOP data but otherwise ignored.
*
* @example
* // An example EOP data file, EOP.json:
* {
* "columnNames" : ["dateIso8601","modifiedJulianDateUtc","xPoleWanderRadians","yPoleWanderRadians","ut1MinusUtcSeconds","lengthOfDayCorrectionSeconds","xCelestialPoleOffsetRadians","yCelestialPoleOffsetRadians","taiMinusUtcSeconds"],
* "samples" : [
* "2011-07-01T00:00:00Z",55743.0,2.117957047295119e-7,2.111518721609984e-6,-0.2908948,-2.956e-4,3.393695767766752e-11,3.3452143996557983e-10,34.0,
* "2011-07-02T00:00:00Z",55744.0,2.193297093339541e-7,2.115460256837405e-6,-0.29065,-1.824e-4,-8.241832578862112e-11,5.623838700870617e-10,34.0,
* "2011-07-03T00:00:00Z",55745.0,2.262286080161428e-7,2.1191157519929706e-6,-0.2905572,1.9e-6,-3.490658503988659e-10,6.981317007977318e-10,34.0
* ]
* }
*
* @example
* // Loading the EOP data
* var eop = new Cesium.EarthOrientationParameters({ url : 'Data/EOP.json' });
* Cesium.Transforms.earthOrientationParameters = eop;
*
* @private
*/
function EarthOrientationParameters(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._dates = undefined;
this._samples = undefined;
this._dateColumn = -1;
this._xPoleWanderRadiansColumn = -1;
this._yPoleWanderRadiansColumn = -1;
this._ut1MinusUtcSecondsColumn = -1;
this._xCelestialPoleOffsetRadiansColumn = -1;
this._yCelestialPoleOffsetRadiansColumn = -1;
this._taiMinusUtcSecondsColumn = -1;
this._columnCount = 0;
this._lastIndex = -1;
this._downloadPromise = undefined;
this._dataError = undefined;
this._addNewLeapSeconds = defaultValue(options.addNewLeapSeconds, true);
if (defined(options.data)) {
// Use supplied EOP data.
onDataReady(this, options.data);
} else if (defined(options.url)) {
// Download EOP data.
var that = this;
this._downloadPromise = when(loadJson(options.url), function(eopData) {
onDataReady(that, eopData);
}, function() {
that._dataError = 'An error occurred while retrieving the EOP data from the URL ' + options.url + '.';
});
} else {
// Use all zeros for EOP data.
onDataReady(this, {
'columnNames' : ['dateIso8601', 'modifiedJulianDateUtc', 'xPoleWanderRadians', 'yPoleWanderRadians', 'ut1MinusUtcSeconds', 'lengthOfDayCorrectionSeconds', 'xCelestialPoleOffsetRadians', 'yCelestialPoleOffsetRadians', 'taiMinusUtcSeconds'],
'samples' : []
});
}
}
/**
* A default {@link EarthOrientationParameters} instance that returns zero for all EOP values.
*/
EarthOrientationParameters.NONE = freezeObject({
getPromiseToLoad : function() {
return when();
},
compute : function(date, result) {
if (!defined(result)) {
result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
} else {
result.xPoleWander = 0.0;
result.yPoleWander = 0.0;
result.xPoleOffset = 0.0;
result.yPoleOffset = 0.0;
result.ut1MinusUtc = 0.0;
}
return result;
}
});
/**
* Gets a promise that, when resolved, indicates that the EOP data has been loaded and is
* ready to use.
*
* @returns {Promise.} The promise.
*
* @see when
*/
EarthOrientationParameters.prototype.getPromiseToLoad = function() {
return when(this._downloadPromise);
};
/**
* Computes the Earth Orientation Parameters (EOP) for a given date by interpolating.
* If the EOP data has not yet been download, this method returns undefined.
*
* @param {JulianDate} date The date for each to evaluate the EOP.
* @param {EarthOrientationParametersSample} [result] The instance to which to copy the result.
* If this parameter is undefined, a new instance is created and returned.
* @returns {EarthOrientationParametersSample} The EOP evaluated at the given date, or
* undefined if the data necessary to evaluate EOP at the date has not yet been
* downloaded.
*
* @exception {RuntimeError} The loaded EOP data has an error and cannot be used.
*
* @see EarthOrientationParameters#getPromiseToLoad
*/
EarthOrientationParameters.prototype.compute = function(date, result) {
// We cannot compute until the samples are available.
if (!defined(this._samples)) {
if (defined(this._dataError)) {
throw new RuntimeError(this._dataError);
}
return undefined;
}
if (!defined(result)) {
result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
}
if (this._samples.length === 0) {
result.xPoleWander = 0.0;
result.yPoleWander = 0.0;
result.xPoleOffset = 0.0;
result.yPoleOffset = 0.0;
result.ut1MinusUtc = 0.0;
return result;
}
var dates = this._dates;
var lastIndex = this._lastIndex;
var before = 0;
var after = 0;
if (defined(lastIndex)) {
var previousIndexDate = dates[lastIndex];
var nextIndexDate = dates[lastIndex + 1];
var isAfterPrevious = JulianDate.lessThanOrEquals(previousIndexDate, date);
var isAfterLastSample = !defined(nextIndexDate);
var isBeforeNext = isAfterLastSample || JulianDate.greaterThanOrEquals(nextIndexDate, date);
if (isAfterPrevious && isBeforeNext) {
before = lastIndex;
if (!isAfterLastSample && nextIndexDate.equals(date)) {
++before;
}
after = before + 1;
interpolate(this, dates, this._samples, date, before, after, result);
return result;
}
}
var index = binarySearch(dates, date, JulianDate.compare, this._dateColumn);
if (index >= 0) {
// If the next entry is the same date, use the later entry. This way, if two entries
// describe the same moment, one before a leap second and the other after, then we will use
// the post-leap second data.
if (index < dates.length - 1 && dates[index + 1].equals(date)) {
++index;
}
before = index;
after = index;
} else {
after = ~index;
before = after - 1;
// Use the first entry if the date requested is before the beginning of the data.
if (before < 0) {
before = 0;
}
}
this._lastIndex = before;
interpolate(this, dates, this._samples, date, before, after, result);
return result;
};
function compareLeapSecondDates(leapSecond, dateToFind) {
return JulianDate.compare(leapSecond.julianDate, dateToFind);
}
function onDataReady(eop, eopData) {
if (!defined(eopData.columnNames)) {
eop._dataError = 'Error in loaded EOP data: The columnNames property is required.';
return;
}
if (!defined(eopData.samples)) {
eop._dataError = 'Error in loaded EOP data: The samples property is required.';
return;
}
var dateColumn = eopData.columnNames.indexOf('modifiedJulianDateUtc');
var xPoleWanderRadiansColumn = eopData.columnNames.indexOf('xPoleWanderRadians');
var yPoleWanderRadiansColumn = eopData.columnNames.indexOf('yPoleWanderRadians');
var ut1MinusUtcSecondsColumn = eopData.columnNames.indexOf('ut1MinusUtcSeconds');
var xCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf('xCelestialPoleOffsetRadians');
var yCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf('yCelestialPoleOffsetRadians');
var taiMinusUtcSecondsColumn = eopData.columnNames.indexOf('taiMinusUtcSeconds');
if (dateColumn < 0 || xPoleWanderRadiansColumn < 0 || yPoleWanderRadiansColumn < 0 || ut1MinusUtcSecondsColumn < 0 || xCelestialPoleOffsetRadiansColumn < 0 || yCelestialPoleOffsetRadiansColumn < 0 || taiMinusUtcSecondsColumn < 0) {
eop._dataError = 'Error in loaded EOP data: The columnNames property must include modifiedJulianDateUtc, xPoleWanderRadians, yPoleWanderRadians, ut1MinusUtcSeconds, xCelestialPoleOffsetRadians, yCelestialPoleOffsetRadians, and taiMinusUtcSeconds columns';
return;
}
var samples = eop._samples = eopData.samples;
var dates = eop._dates = [];
eop._dateColumn = dateColumn;
eop._xPoleWanderRadiansColumn = xPoleWanderRadiansColumn;
eop._yPoleWanderRadiansColumn = yPoleWanderRadiansColumn;
eop._ut1MinusUtcSecondsColumn = ut1MinusUtcSecondsColumn;
eop._xCelestialPoleOffsetRadiansColumn = xCelestialPoleOffsetRadiansColumn;
eop._yCelestialPoleOffsetRadiansColumn = yCelestialPoleOffsetRadiansColumn;
eop._taiMinusUtcSecondsColumn = taiMinusUtcSecondsColumn;
eop._columnCount = eopData.columnNames.length;
eop._lastIndex = undefined;
var lastTaiMinusUtc;
var addNewLeapSeconds = eop._addNewLeapSeconds;
// Convert the ISO8601 dates to JulianDates.
for (var i = 0, len = samples.length; i < len; i += eop._columnCount) {
var mjd = samples[i + dateColumn];
var taiMinusUtc = samples[i + taiMinusUtcSecondsColumn];
var day = mjd + TimeConstants.MODIFIED_JULIAN_DATE_DIFFERENCE;
var date = new JulianDate(day, taiMinusUtc, TimeStandard.TAI);
dates.push(date);
if (addNewLeapSeconds) {
if (taiMinusUtc !== lastTaiMinusUtc && defined(lastTaiMinusUtc)) {
// We crossed a leap second boundary, so add the leap second
// if it does not already exist.
var leapSeconds = JulianDate.leapSeconds;
var leapSecondIndex = binarySearch(leapSeconds, date, compareLeapSecondDates);
if (leapSecondIndex < 0) {
var leapSecond = new LeapSecond(date, taiMinusUtc);
leapSeconds.splice(~leapSecondIndex, 0, leapSecond);
}
}
lastTaiMinusUtc = taiMinusUtc;
}
}
}
function fillResultFromIndex(eop, samples, index, columnCount, result) {
var start = index * columnCount;
result.xPoleWander = samples[start + eop._xPoleWanderRadiansColumn];
result.yPoleWander = samples[start + eop._yPoleWanderRadiansColumn];
result.xPoleOffset = samples[start + eop._xCelestialPoleOffsetRadiansColumn];
result.yPoleOffset = samples[start + eop._yCelestialPoleOffsetRadiansColumn];
result.ut1MinusUtc = samples[start + eop._ut1MinusUtcSecondsColumn];
}
function linearInterp(dx, y1, y2) {
return y1 + dx * (y2 - y1);
}
function interpolate(eop, dates, samples, date, before, after, result) {
var columnCount = eop._columnCount;
// First check the bounds on the EOP data
// If we are after the bounds of the data, return zeros.
// The 'before' index should never be less than zero.
if (after > dates.length - 1) {
result.xPoleWander = 0;
result.yPoleWander = 0;
result.xPoleOffset = 0;
result.yPoleOffset = 0;
result.ut1MinusUtc = 0;
return result;
}
var beforeDate = dates[before];
var afterDate = dates[after];
if (beforeDate.equals(afterDate) || date.equals(beforeDate)) {
fillResultFromIndex(eop, samples, before, columnCount, result);
return result;
} else if (date.equals(afterDate)) {
fillResultFromIndex(eop, samples, after, columnCount, result);
return result;
}
var factor = JulianDate.secondsDifference(date, beforeDate) / JulianDate.secondsDifference(afterDate, beforeDate);
var startBefore = before * columnCount;
var startAfter = after * columnCount;
// Handle UT1 leap second edge case
var beforeUt1MinusUtc = samples[startBefore + eop._ut1MinusUtcSecondsColumn];
var afterUt1MinusUtc = samples[startAfter + eop._ut1MinusUtcSecondsColumn];
var offsetDifference = afterUt1MinusUtc - beforeUt1MinusUtc;
if (offsetDifference > 0.5 || offsetDifference < -0.5) {
// The absolute difference between the values is more than 0.5, so we may have
// crossed a leap second. Check if this is the case and, if so, adjust the
// afterValue to account for the leap second. This way, our interpolation will
// produce reasonable results.
var beforeTaiMinusUtc = samples[startBefore + eop._taiMinusUtcSecondsColumn];
var afterTaiMinusUtc = samples[startAfter + eop._taiMinusUtcSecondsColumn];
if (beforeTaiMinusUtc !== afterTaiMinusUtc) {
if (afterDate.equals(date)) {
// If we are at the end of the leap second interval, take the second value
// Otherwise, the interpolation below will yield the wrong side of the
// discontinuity
// At the end of the leap second, we need to start accounting for the jump
beforeUt1MinusUtc = afterUt1MinusUtc;
} else {
// Otherwise, remove the leap second so that the interpolation is correct
afterUt1MinusUtc -= afterTaiMinusUtc - beforeTaiMinusUtc;
}
}
}
result.xPoleWander = linearInterp(factor, samples[startBefore + eop._xPoleWanderRadiansColumn], samples[startAfter + eop._xPoleWanderRadiansColumn]);
result.yPoleWander = linearInterp(factor, samples[startBefore + eop._yPoleWanderRadiansColumn], samples[startAfter + eop._yPoleWanderRadiansColumn]);
result.xPoleOffset = linearInterp(factor, samples[startBefore + eop._xCelestialPoleOffsetRadiansColumn], samples[startAfter + eop._xCelestialPoleOffsetRadiansColumn]);
result.yPoleOffset = linearInterp(factor, samples[startBefore + eop._yCelestialPoleOffsetRadiansColumn], samples[startAfter + eop._yCelestialPoleOffsetRadiansColumn]);
result.ut1MinusUtc = linearInterp(factor, beforeUt1MinusUtc, afterUt1MinusUtc);
return result;
}
return EarthOrientationParameters;
});
/*global define*/
define('Core/HeadingPitchRoll',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./Math'
], function(
defaultValue,
defined,
DeveloperError,
CesiumMath) {
"use strict";
/**
* A rotation expressed as a heading, pitch, and roll. Heading is the rotation about the
* negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about
* the positive x axis.
* @alias HeadingPitchRoll
* @constructor
*
* @param {Number} [heading=0.0] The heading component in radians.
* @param {Number} [pitch=0.0] The pitch component in radians.
* @param {Number} [roll=0.0] The roll component in radians.
*/
function HeadingPitchRoll(heading, pitch, roll) {
this.heading = defaultValue(heading, 0.0);
this.pitch = defaultValue(pitch, 0.0);
this.roll = defaultValue(roll, 0.0);
}
/**
* Computes the heading, pitch and roll from a quaternion (see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles )
*
* @param {Quaternion} quaternion The quaternion from which to retrieve heading, pitch, and roll, all expressed in radians.
* @param {Quaternion} [result] The object in which to store the result. If not provided, a new instance is created and returned.
* @returns {HeadingPitchRoll} The modified result parameter or a new HeadingPitchRoll instance if one was not provided.
*/
HeadingPitchRoll.fromQuaternion = function(quaternion, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (!defined(result)) {
result = new HeadingPitchRoll();
}
var test = 2 * (quaternion.w * quaternion.y - quaternion.z * quaternion.x);
var denominatorRoll = 1 - 2 * (quaternion.x * quaternion.x + quaternion.y * quaternion.y);
var numeratorRoll = 2 * (quaternion.w * quaternion.x + quaternion.y * quaternion.z);
var denominatorHeading = 1 - 2 * (quaternion.y * quaternion.y + quaternion.z * quaternion.z);
var numeratorHeading = 2 * (quaternion.w * quaternion.z + quaternion.x * quaternion.y);
result.heading = -Math.atan2(numeratorHeading, denominatorHeading);
result.roll = Math.atan2(numeratorRoll, denominatorRoll);
result.pitch = -Math.asin(test);
return result;
};
/**
* Returns a new HeadingPitchRoll instance from angles given in degrees.
*
* @param {Number} heading the heading in degrees
* @param {Number} pitch the pitch in degrees
* @param {Number} roll the heading in degrees
* @param {HeadingPitchRoll} [result] The object in which to store the result. If not provided, a new instance is created and returned.
* @returns {HeadingPitchRoll} A new HeadingPitchRoll instance
*/
HeadingPitchRoll.fromDegrees = function(heading, pitch, roll, result) {
if (!defined(heading)) {
throw new DeveloperError('heading is required');
}
if (!defined(pitch)) {
throw new DeveloperError('pitch is required');
}
if (!defined(roll)) {
throw new DeveloperError('roll is required');
}
if (!defined(result)) {
result = new HeadingPitchRoll();
}
result.heading = heading * CesiumMath.RADIANS_PER_DEGREE;
result.pitch = pitch * CesiumMath.RADIANS_PER_DEGREE;
result.roll = roll * CesiumMath.RADIANS_PER_DEGREE;
return result;
};
/**
* Duplicates a HeadingPitchRoll instance.
*
* @param {HeadingPitchRoll} headingPitchRoll The HeadingPitchRoll to duplicate.
* @param {HeadingPitchRoll} [result] The object onto which to store the result.
* @returns {HeadingPitchRoll} The modified result parameter or a new HeadingPitchRoll instance if one was not provided. (Returns undefined if headingPitchRoll is undefined)
*/
HeadingPitchRoll.clone = function(headingPitchRoll, result) {
if (!defined(headingPitchRoll)) {
return undefined;
}
if (!defined(result)) {
return new HeadingPitchRoll(headingPitchRoll.heading, headingPitchRoll.pitch, headingPitchRoll.roll);
}
result.heading = headingPitchRoll.heading;
result.pitch = headingPitchRoll.pitch;
result.roll = headingPitchRoll.roll;
return result;
};
/**
* Compares the provided HeadingPitchRolls componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {HeadingPitchRoll} [left] The first HeadingPitchRoll.
* @param {HeadingPitchRoll} [right] The second HeadingPitchRoll.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
HeadingPitchRoll.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.heading === right.heading) &&
(left.pitch === right.pitch) &&
(left.roll === right.roll));
};
/**
* Compares the provided HeadingPitchRolls componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {HeadingPitchRoll} [left] The first HeadingPitchRoll.
* @param {HeadingPitchRoll} [right] The second HeadingPitchRoll.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
HeadingPitchRoll.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
return (left === right) ||
(defined(left) &&
defined(right) &&
CesiumMath.equalsEpsilon(left.heading, right.heading, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.pitch, right.pitch, relativeEpsilon, absoluteEpsilon) &&
CesiumMath.equalsEpsilon(left.roll, right.roll, relativeEpsilon, absoluteEpsilon));
};
/**
* Duplicates this HeadingPitchRoll instance.
*
* @param {HeadingPitchRoll} [result] The object onto which to store the result.
* @returns {HeadingPitchRoll} The modified result parameter or a new HeadingPitchRoll instance if one was not provided.
*/
HeadingPitchRoll.prototype.clone = function(result) {
return HeadingPitchRoll.clone(this, result);
};
/**
* Compares this HeadingPitchRoll against the provided HeadingPitchRoll componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {HeadingPitchRoll} [right] The right hand side HeadingPitchRoll.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
HeadingPitchRoll.prototype.equals = function(right) {
return HeadingPitchRoll.equals(this, right);
};
/**
* Compares this HeadingPitchRoll against the provided HeadingPitchRoll componentwise and returns
* true
if they pass an absolute or relative tolerance test,
* false
otherwise.
*
* @param {HeadingPitchRoll} [right] The right hand side HeadingPitchRoll.
* @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
* @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
HeadingPitchRoll.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
return HeadingPitchRoll.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
};
/**
* Creates a string representing this HeadingPitchRoll in the format '(heading, pitch, roll)' in radians.
*
* @returns {String} A string representing the provided HeadingPitchRoll in the format '(heading, pitch, roll)'.
*/
HeadingPitchRoll.prototype.toString = function() {
return '(' + this.heading + ', ' + this.pitch + ', ' + this.roll + ')';
};
return HeadingPitchRoll;
});
/*global define*/
define('Core/getAbsoluteUri',[
'../ThirdParty/Uri',
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
Uri,
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Given a relative Uri and a base Uri, returns the absolute Uri of the relative Uri.
* @exports getAbsoluteUri
*
* @param {String} relative The relative Uri.
* @param {String} [base] The base Uri.
* @returns {String} The absolute Uri of the given relative Uri.
*
* @example
* //absolute Uri will be "https://test.com/awesome.png";
* var absoluteUri = Cesium.getAbsoluteUri('awesome.png', 'https://test.com');
*/
function getAbsoluteUri(relative, base) {
if (!defined(relative)) {
throw new DeveloperError('relative uri is required.');
}
base = defaultValue(base, document.location.href);
var baseUri = new Uri(base);
var relativeUri = new Uri(relative);
return relativeUri.resolve(baseUri).toString();
}
return getAbsoluteUri;
});
/*global define*/
define('Core/joinUrls',[
'../ThirdParty/Uri',
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
Uri,
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Function for joining URLs in a manner that is aware of query strings and fragments.
* This is useful when the base URL has a query string that needs to be maintained
* (e.g. a presigned base URL).
* @param {String|Uri} first The base URL.
* @param {String|Uri} second The URL path to join to the base URL. If this URL is absolute, it is returned unmodified.
* @param {Boolean} [appendSlash=true] The boolean determining whether there should be a forward slash between first and second.
* @private
*/
function joinUrls(first, second, appendSlash) {
if (!defined(first)) {
throw new DeveloperError('first is required');
}
if (!defined(second)) {
throw new DeveloperError('second is required');
}
appendSlash = defaultValue(appendSlash, true);
if (!(first instanceof Uri)) {
first = new Uri(first);
}
if (!(second instanceof Uri)) {
second = new Uri(second);
}
// Uri.isAbsolute returns false for a URL like '//foo.com'. So if we have an authority but
// not a scheme, add a scheme matching the page's scheme.
if (defined(second.authority) && !defined(second.scheme)) {
if (typeof document !== 'undefined' && defined(document.location) && defined(document.location.href)) {
second.scheme = new Uri(document.location.href).scheme;
} else {
// Not in a browser? Use the first URL's scheme instead.
second.scheme = first.scheme;
}
}
// If the second URL is absolute, use it for the scheme, authority, and path.
var baseUri = first;
if (second.isAbsolute()) {
baseUri = second;
}
var url = '';
if (defined(baseUri.scheme)) {
url += baseUri.scheme + ':';
}
if (defined(baseUri.authority)) {
url += '//' + baseUri.authority;
if (baseUri.path !== '' && baseUri.path !== '/') {
url = url.replace(/\/?$/, '/');
baseUri.path = baseUri.path.replace(/^\/?/g, '');
}
}
// Combine the paths (only if second is relative).
if (baseUri === first) {
if (appendSlash) {
url += first.path.replace(/\/?$/, '/') + second.path.replace(/^\/?/g, '');
} else {
url += first.path + second.path;
}
} else {
url += second.path;
}
// Combine the queries and fragments.
var hasFirstQuery = defined(first.query);
var hasSecondQuery = defined(second.query);
if (hasFirstQuery && hasSecondQuery) {
url += '?' + first.query + '&' + second.query;
} else if (hasFirstQuery && !hasSecondQuery) {
url += '?' + first.query;
} else if (!hasFirstQuery && hasSecondQuery) {
url += '?' + second.query;
}
var hasSecondFragment = defined(second.fragment);
if (defined(first.fragment) && !hasSecondFragment) {
url += '#' + first.fragment;
} else if (hasSecondFragment) {
url += '#' + second.fragment;
}
return url;
}
return joinUrls;
});
/*global define*/
define('Core/buildModuleUrl',[
'../ThirdParty/Uri',
'./defined',
'./DeveloperError',
'./getAbsoluteUri',
'./joinUrls',
'require'
], function(
Uri,
defined,
DeveloperError,
getAbsoluteUri,
joinUrls,
require) {
'use strict';
/*global CESIUM_BASE_URL*/
var cesiumScriptRegex = /((?:.*\/)|^)cesium[\w-]*\.js(?:\W|$)/i;
function getBaseUrlFromCesiumScript() {
var scripts = document.getElementsByTagName('script');
for ( var i = 0, len = scripts.length; i < len; ++i) {
var src = scripts[i].getAttribute('src');
var result = cesiumScriptRegex.exec(src);
if (result !== null) {
return result[1];
}
}
return undefined;
}
var baseUrl;
function getCesiumBaseUrl() {
if (defined(baseUrl)) {
return baseUrl;
}
var baseUrlString;
if (typeof CESIUM_BASE_URL !== 'undefined') {
baseUrlString = CESIUM_BASE_URL;
} else {
baseUrlString = getBaseUrlFromCesiumScript();
}
if (!defined(baseUrlString)) {
throw new DeveloperError('Unable to determine Cesium base URL automatically, try defining a global variable called CESIUM_BASE_URL.');
}
baseUrl = new Uri(getAbsoluteUri(baseUrlString));
return baseUrl;
}
function buildModuleUrlFromRequireToUrl(moduleID) {
//moduleID will be non-relative, so require it relative to this module, in Core.
return require.toUrl('../' + moduleID);
}
function buildModuleUrlFromBaseUrl(moduleID) {
return joinUrls(getCesiumBaseUrl(), moduleID);
}
var implementation;
var a;
/**
* Given a non-relative moduleID, returns an absolute URL to the file represented by that module ID,
* using, in order of preference, require.toUrl, the value of a global CESIUM_BASE_URL, or
* the base URL of the Cesium.js script.
*
* @private
*/
function buildModuleUrl(moduleID) {
if (!defined(implementation)) {
//select implementation
if (defined(require.toUrl)) {
implementation = buildModuleUrlFromRequireToUrl;
} else {
implementation = buildModuleUrlFromBaseUrl;
}
}
if (!defined(a)) {
a = document.createElement('a');
}
var url = implementation(moduleID);
a.href = url;
a.href = a.href; // IE only absolutizes href on get, not set
return a.href;
}
// exposed for testing
buildModuleUrl._cesiumScriptRegex = cesiumScriptRegex;
/**
* Sets the base URL for resolving modules.
* @param {String} value The new base URL.
*/
buildModuleUrl.setBaseUrl = function(value) {
baseUrl = new Uri(value).resolve(new Uri(document.location.href));
};
return buildModuleUrl;
});
/*global define*/
define('Core/Iau2006XysSample',[],function() {
'use strict';
/**
* An IAU 2006 XYS value sampled at a particular time.
*
* @alias Iau2006XysSample
* @constructor
*
* @param {Number} x The X value.
* @param {Number} y The Y value.
* @param {Number} s The S value.
*
* @private
*/
function Iau2006XysSample(x, y, s) {
/**
* The X value.
* @type {Number}
*/
this.x = x;
/**
* The Y value.
* @type {Number}
*/
this.y = y;
/**
* The S value.
* @type {Number}
*/
this.s = s;
}
return Iau2006XysSample;
});
/*global define*/
define('Core/Iau2006XysData',[
'../ThirdParty/when',
'./buildModuleUrl',
'./defaultValue',
'./defined',
'./Iau2006XysSample',
'./JulianDate',
'./loadJson',
'./TimeStandard'
], function(
when,
buildModuleUrl,
defaultValue,
defined,
Iau2006XysSample,
JulianDate,
loadJson,
TimeStandard) {
'use strict';
/**
* A set of IAU2006 XYS data that is used to evaluate the transformation between the International
* Celestial Reference Frame (ICRF) and the International Terrestrial Reference Frame (ITRF).
*
* @alias Iau2006XysData
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {String} [options.xysFileUrlTemplate='Assets/IAU2006_XYS/IAU2006_XYS_{0}.json'] A template URL for obtaining the XYS data. In the template,
* `{0}` will be replaced with the file index.
* @param {Number} [options.interpolationOrder=9] The order of interpolation to perform on the XYS data.
* @param {Number} [options.sampleZeroJulianEphemerisDate=2442396.5] The Julian ephemeris date (JED) of the
* first XYS sample.
* @param {Number} [options.stepSizeDays=1.0] The step size, in days, between successive XYS samples.
* @param {Number} [options.samplesPerXysFile=1000] The number of samples in each XYS file.
* @param {Number} [options.totalSamples=27426] The total number of samples in all XYS files.
*
* @private
*/
function Iau2006XysData(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._xysFileUrlTemplate = options.xysFileUrlTemplate;
this._interpolationOrder = defaultValue(options.interpolationOrder, 9);
this._sampleZeroJulianEphemerisDate = defaultValue(options.sampleZeroJulianEphemerisDate, 2442396.5);
this._sampleZeroDateTT = new JulianDate(this._sampleZeroJulianEphemerisDate, 0.0, TimeStandard.TAI);
this._stepSizeDays = defaultValue(options.stepSizeDays, 1.0);
this._samplesPerXysFile = defaultValue(options.samplesPerXysFile, 1000);
this._totalSamples = defaultValue(options.totalSamples, 27426);
this._samples = new Array(this._totalSamples * 3);
this._chunkDownloadsInProgress = [];
var order = this._interpolationOrder;
// Compute denominators and X values for interpolation.
var denom = this._denominators = new Array(order + 1);
var xTable = this._xTable = new Array(order + 1);
var stepN = Math.pow(this._stepSizeDays, order);
for ( var i = 0; i <= order; ++i) {
denom[i] = stepN;
xTable[i] = i * this._stepSizeDays;
for ( var j = 0; j <= order; ++j) {
if (j !== i) {
denom[i] *= (i - j);
}
}
denom[i] = 1.0 / denom[i];
}
// Allocate scratch arrays for interpolation.
this._work = new Array(order + 1);
this._coef = new Array(order + 1);
}
var julianDateScratch = new JulianDate(0, 0.0, TimeStandard.TAI);
function getDaysSinceEpoch(xys, dayTT, secondTT) {
var dateTT = julianDateScratch;
dateTT.dayNumber = dayTT;
dateTT.secondsOfDay = secondTT;
return JulianDate.daysDifference(dateTT, xys._sampleZeroDateTT);
}
/**
* Preloads XYS data for a specified date range.
*
* @param {Number} startDayTT The Julian day number of the beginning of the interval to preload, expressed in
* the Terrestrial Time (TT) time standard.
* @param {Number} startSecondTT The seconds past noon of the beginning of the interval to preload, expressed in
* the Terrestrial Time (TT) time standard.
* @param {Number} stopDayTT The Julian day number of the end of the interval to preload, expressed in
* the Terrestrial Time (TT) time standard.
* @param {Number} stopSecondTT The seconds past noon of the end of the interval to preload, expressed in
* the Terrestrial Time (TT) time standard.
* @returns {Promise.} A promise that, when resolved, indicates that the requested interval has been
* preloaded.
*/
Iau2006XysData.prototype.preload = function(startDayTT, startSecondTT, stopDayTT, stopSecondTT) {
var startDaysSinceEpoch = getDaysSinceEpoch(this, startDayTT, startSecondTT);
var stopDaysSinceEpoch = getDaysSinceEpoch(this, stopDayTT, stopSecondTT);
var startIndex = (startDaysSinceEpoch / this._stepSizeDays - this._interpolationOrder / 2) | 0;
if (startIndex < 0) {
startIndex = 0;
}
var stopIndex = (stopDaysSinceEpoch / this._stepSizeDays - this._interpolationOrder / 2) | 0 + this._interpolationOrder;
if (stopIndex >= this._totalSamples) {
stopIndex = this._totalSamples - 1;
}
var startChunk = (startIndex / this._samplesPerXysFile) | 0;
var stopChunk = (stopIndex / this._samplesPerXysFile) | 0;
var promises = [];
for ( var i = startChunk; i <= stopChunk; ++i) {
promises.push(requestXysChunk(this, i));
}
return when.all(promises);
};
/**
* Computes the XYS values for a given date by interpolating. If the required data is not yet downloaded,
* this method will return undefined.
*
* @param {Number} dayTT The Julian day number for which to compute the XYS value, expressed in
* the Terrestrial Time (TT) time standard.
* @param {Number} secondTT The seconds past noon of the date for which to compute the XYS value, expressed in
* the Terrestrial Time (TT) time standard.
* @param {Iau2006XysSample} [result] The instance to which to copy the interpolated result. If this parameter
* is undefined, a new instance is allocated and returned.
* @returns {Iau2006XysSample} The interpolated XYS values, or undefined if the required data for this
* computation has not yet been downloaded.
*
* @see Iau2006XysData#preload
*/
Iau2006XysData.prototype.computeXysRadians = function(dayTT, secondTT, result) {
var daysSinceEpoch = getDaysSinceEpoch(this, dayTT, secondTT);
if (daysSinceEpoch < 0.0) {
// Can't evaluate prior to the epoch of the data.
return undefined;
}
var centerIndex = (daysSinceEpoch / this._stepSizeDays) | 0;
if (centerIndex >= this._totalSamples) {
// Can't evaluate after the last sample in the data.
return undefined;
}
var degree = this._interpolationOrder;
var firstIndex = centerIndex - ((degree / 2) | 0);
if (firstIndex < 0) {
firstIndex = 0;
}
var lastIndex = firstIndex + degree;
if (lastIndex >= this._totalSamples) {
lastIndex = this._totalSamples - 1;
firstIndex = lastIndex - degree;
if (firstIndex < 0) {
firstIndex = 0;
}
}
// Are all the samples we need present?
// We can assume so if the first and last are present
var isDataMissing = false;
var samples = this._samples;
if (!defined(samples[firstIndex * 3])) {
requestXysChunk(this, (firstIndex / this._samplesPerXysFile) | 0);
isDataMissing = true;
}
if (!defined(samples[lastIndex * 3])) {
requestXysChunk(this, (lastIndex / this._samplesPerXysFile) | 0);
isDataMissing = true;
}
if (isDataMissing) {
return undefined;
}
if (!defined(result)) {
result = new Iau2006XysSample(0.0, 0.0, 0.0);
} else {
result.x = 0.0;
result.y = 0.0;
result.s = 0.0;
}
var x = daysSinceEpoch - firstIndex * this._stepSizeDays;
var work = this._work;
var denom = this._denominators;
var coef = this._coef;
var xTable = this._xTable;
var i, j;
for (i = 0; i <= degree; ++i) {
work[i] = x - xTable[i];
}
for (i = 0; i <= degree; ++i) {
coef[i] = 1.0;
for (j = 0; j <= degree; ++j) {
if (j !== i) {
coef[i] *= work[j];
}
}
coef[i] *= denom[i];
var sampleIndex = (firstIndex + i) * 3;
result.x += coef[i] * samples[sampleIndex++];
result.y += coef[i] * samples[sampleIndex++];
result.s += coef[i] * samples[sampleIndex];
}
return result;
};
function requestXysChunk(xysData, chunkIndex) {
if (xysData._chunkDownloadsInProgress[chunkIndex]) {
// Chunk has already been requested.
return xysData._chunkDownloadsInProgress[chunkIndex];
}
var deferred = when.defer();
xysData._chunkDownloadsInProgress[chunkIndex] = deferred;
var chunkUrl;
var xysFileUrlTemplate = xysData._xysFileUrlTemplate;
if (defined(xysFileUrlTemplate)) {
chunkUrl = xysFileUrlTemplate.replace('{0}', chunkIndex);
} else {
chunkUrl = buildModuleUrl('Assets/IAU2006_XYS/IAU2006_XYS_' + chunkIndex + '.json');
}
when(loadJson(chunkUrl), function(chunk) {
xysData._chunkDownloadsInProgress[chunkIndex] = false;
var samples = xysData._samples;
var newSamples = chunk.samples;
var startIndex = chunkIndex * xysData._samplesPerXysFile * 3;
for ( var i = 0, len = newSamples.length; i < len; ++i) {
samples[startIndex + i] = newSamples[i];
}
deferred.resolve();
});
return deferred.promise;
}
return Iau2006XysData;
});
/*global define*/
define('Core/Fullscreen',[
'./defined',
'./defineProperties'
], function(
defined,
defineProperties) {
'use strict';
var _supportsFullscreen;
var _names = {
requestFullscreen : undefined,
exitFullscreen : undefined,
fullscreenEnabled : undefined,
fullscreenElement : undefined,
fullscreenchange : undefined,
fullscreenerror : undefined
};
/**
* Browser-independent functions for working with the standard fullscreen API.
*
* @exports Fullscreen
*
* @see {@link http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html|W3C Fullscreen Living Specification}
*/
var Fullscreen = {};
defineProperties(Fullscreen, {
/**
* The element that is currently fullscreen, if any. To simply check if the
* browser is in fullscreen mode or not, use {@link Fullscreen#fullscreen}.
* @memberof Fullscreen
* @type {Object}
* @readonly
*/
element : {
get : function() {
if (!Fullscreen.supportsFullscreen()) {
return undefined;
}
return document[_names.fullscreenElement];
}
},
/**
* The name of the event on the document that is fired when fullscreen is
* entered or exited. This event name is intended for use with addEventListener.
* In your event handler, to determine if the browser is in fullscreen mode or not,
* use {@link Fullscreen#fullscreen}.
* @memberof Fullscreen
* @type {String}
* @readonly
*/
changeEventName : {
get : function() {
if (!Fullscreen.supportsFullscreen()) {
return undefined;
}
return _names.fullscreenchange;
}
},
/**
* The name of the event that is fired when a fullscreen error
* occurs. This event name is intended for use with addEventListener.
* @memberof Fullscreen
* @type {String}
* @readonly
*/
errorEventName : {
get : function() {
if (!Fullscreen.supportsFullscreen()) {
return undefined;
}
return _names.fullscreenerror;
}
},
/**
* Determine whether the browser will allow an element to be made fullscreen, or not.
* For example, by default, iframes cannot go fullscreen unless the containing page
* adds an "allowfullscreen" attribute (or prefixed equivalent).
* @memberof Fullscreen
* @type {Boolean}
* @readonly
*/
enabled : {
get : function() {
if (!Fullscreen.supportsFullscreen()) {
return undefined;
}
return document[_names.fullscreenEnabled];
}
},
/**
* Determines if the browser is currently in fullscreen mode.
* @memberof Fullscreen
* @type {Boolean}
* @readonly
*/
fullscreen : {
get : function() {
if (!Fullscreen.supportsFullscreen()) {
return undefined;
}
return Fullscreen.element !== null;
}
}
});
/**
* Detects whether the browser supports the standard fullscreen API.
*
* @returns {Boolean} true
if the browser supports the standard fullscreen API,
* false
otherwise.
*/
Fullscreen.supportsFullscreen = function() {
if (defined(_supportsFullscreen)) {
return _supportsFullscreen;
}
_supportsFullscreen = false;
var body = document.body;
if (typeof body.requestFullscreen === 'function') {
// go with the unprefixed, standard set of names
_names.requestFullscreen = 'requestFullscreen';
_names.exitFullscreen = 'exitFullscreen';
_names.fullscreenEnabled = 'fullscreenEnabled';
_names.fullscreenElement = 'fullscreenElement';
_names.fullscreenchange = 'fullscreenchange';
_names.fullscreenerror = 'fullscreenerror';
_supportsFullscreen = true;
return _supportsFullscreen;
}
//check for the correct combination of prefix plus the various names that browsers use
var prefixes = ['webkit', 'moz', 'o', 'ms', 'khtml'];
var name;
for (var i = 0, len = prefixes.length; i < len; ++i) {
var prefix = prefixes[i];
// casing of Fullscreen differs across browsers
name = prefix + 'RequestFullscreen';
if (typeof body[name] === 'function') {
_names.requestFullscreen = name;
_supportsFullscreen = true;
} else {
name = prefix + 'RequestFullScreen';
if (typeof body[name] === 'function') {
_names.requestFullscreen = name;
_supportsFullscreen = true;
}
}
// disagreement about whether it's "exit" as per spec, or "cancel"
name = prefix + 'ExitFullscreen';
if (typeof document[name] === 'function') {
_names.exitFullscreen = name;
} else {
name = prefix + 'CancelFullScreen';
if (typeof document[name] === 'function') {
_names.exitFullscreen = name;
}
}
// casing of Fullscreen differs across browsers
name = prefix + 'FullscreenEnabled';
if (document[name] !== undefined) {
_names.fullscreenEnabled = name;
} else {
name = prefix + 'FullScreenEnabled';
if (document[name] !== undefined) {
_names.fullscreenEnabled = name;
}
}
// casing of Fullscreen differs across browsers
name = prefix + 'FullscreenElement';
if (document[name] !== undefined) {
_names.fullscreenElement = name;
} else {
name = prefix + 'FullScreenElement';
if (document[name] !== undefined) {
_names.fullscreenElement = name;
}
}
// thankfully, event names are all lowercase per spec
name = prefix + 'fullscreenchange';
// event names do not have 'on' in the front, but the property on the document does
if (document['on' + name] !== undefined) {
//except on IE
if (prefix === 'ms') {
name = 'MSFullscreenChange';
}
_names.fullscreenchange = name;
}
name = prefix + 'fullscreenerror';
if (document['on' + name] !== undefined) {
//except on IE
if (prefix === 'ms') {
name = 'MSFullscreenError';
}
_names.fullscreenerror = name;
}
}
return _supportsFullscreen;
};
/**
* Asynchronously requests the browser to enter fullscreen mode on the given element.
* If fullscreen mode is not supported by the browser, does nothing.
*
* @param {Object} element The HTML element which will be placed into fullscreen mode.
* @param {HMDVRDevice} [vrDevice] The VR device.
*
* @example
* // Put the entire page into fullscreen.
* Cesium.Fullscreen.requestFullscreen(document.body)
*
* // Place only the Cesium canvas into fullscreen.
* Cesium.Fullscreen.requestFullscreen(scene.canvas)
*/
Fullscreen.requestFullscreen = function(element, vrDevice) {
if (!Fullscreen.supportsFullscreen()) {
return;
}
element[_names.requestFullscreen]({ vrDisplay: vrDevice });
};
/**
* Asynchronously exits fullscreen mode. If the browser is not currently
* in fullscreen, or if fullscreen mode is not supported by the browser, does nothing.
*/
Fullscreen.exitFullscreen = function() {
if (!Fullscreen.supportsFullscreen()) {
return;
}
document[_names.exitFullscreen]();
};
return Fullscreen;
});
/*global define*/
define('Core/FeatureDetection',[
'./defaultValue',
'./defined',
'./Fullscreen'
], function(
defaultValue,
defined,
Fullscreen) {
'use strict';
var theNavigator;
if (typeof navigator !== 'undefined') {
theNavigator = navigator;
} else {
theNavigator = {};
}
function extractVersion(versionString) {
var parts = versionString.split('.');
for (var i = 0, len = parts.length; i < len; ++i) {
parts[i] = parseInt(parts[i], 10);
}
return parts;
}
var isChromeResult;
var chromeVersionResult;
function isChrome() {
if (!defined(isChromeResult)) {
isChromeResult = false;
// Edge contains Chrome in the user agent too
if (!isEdge()) {
var fields = (/ Chrome\/([\.0-9]+)/).exec(theNavigator.userAgent);
if (fields !== null) {
isChromeResult = true;
chromeVersionResult = extractVersion(fields[1]);
}
}
}
return isChromeResult;
}
function chromeVersion() {
return isChrome() && chromeVersionResult;
}
var isSafariResult;
var safariVersionResult;
function isSafari() {
if (!defined(isSafariResult)) {
isSafariResult = false;
// Chrome and Edge contain Safari in the user agent too
if (!isChrome() && !isEdge() && (/ Safari\/[\.0-9]+/).test(theNavigator.userAgent)) {
var fields = (/ Version\/([\.0-9]+)/).exec(theNavigator.userAgent);
if (fields !== null) {
isSafariResult = true;
safariVersionResult = extractVersion(fields[1]);
}
}
}
return isSafariResult;
}
function safariVersion() {
return isSafari() && safariVersionResult;
}
var isWebkitResult;
var webkitVersionResult;
function isWebkit() {
if (!defined(isWebkitResult)) {
isWebkitResult = false;
var fields = (/ AppleWebKit\/([\.0-9]+)(\+?)/).exec(theNavigator.userAgent);
if (fields !== null) {
isWebkitResult = true;
webkitVersionResult = extractVersion(fields[1]);
webkitVersionResult.isNightly = !!fields[2];
}
}
return isWebkitResult;
}
function webkitVersion() {
return isWebkit() && webkitVersionResult;
}
var isInternetExplorerResult;
var internetExplorerVersionResult;
function isInternetExplorer() {
if (!defined(isInternetExplorerResult)) {
isInternetExplorerResult = false;
var fields;
if (theNavigator.appName === 'Microsoft Internet Explorer') {
fields = /MSIE ([0-9]{1,}[\.0-9]{0,})/.exec(theNavigator.userAgent);
if (fields !== null) {
isInternetExplorerResult = true;
internetExplorerVersionResult = extractVersion(fields[1]);
}
} else if (theNavigator.appName === 'Netscape') {
fields = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(theNavigator.userAgent);
if (fields !== null) {
isInternetExplorerResult = true;
internetExplorerVersionResult = extractVersion(fields[1]);
}
}
}
return isInternetExplorerResult;
}
function internetExplorerVersion() {
return isInternetExplorer() && internetExplorerVersionResult;
}
var isEdgeResult;
var edgeVersionResult;
function isEdge() {
if (!defined(isEdgeResult)) {
isEdgeResult = false;
var fields = (/ Edge\/([\.0-9]+)/).exec(theNavigator.userAgent);
if (fields !== null) {
isEdgeResult = true;
edgeVersionResult = extractVersion(fields[1]);
}
}
return isEdgeResult;
}
function edgeVersion() {
return isEdge() && edgeVersionResult;
}
var isFirefoxResult;
var firefoxVersionResult;
function isFirefox() {
if (!defined(isFirefoxResult)) {
isFirefoxResult = false;
var fields = /Firefox\/([\.0-9]+)/.exec(theNavigator.userAgent);
if (fields !== null) {
isFirefoxResult = true;
firefoxVersionResult = extractVersion(fields[1]);
}
}
return isFirefoxResult;
}
var isWindowsResult;
function isWindows() {
if (!defined(isWindowsResult)) {
isWindowsResult = /Windows/i.test(theNavigator.appVersion);
}
return isWindowsResult;
}
function firefoxVersion() {
return isFirefox() && firefoxVersionResult;
}
var hasPointerEvents;
function supportsPointerEvents() {
if (!defined(hasPointerEvents)) {
//While navigator.pointerEnabled is deprecated in the W3C specification
//we still need to use it if it exists in order to support browsers
//that rely on it, such as the Windows WebBrowser control which defines
//PointerEvent but sets navigator.pointerEnabled to false.
hasPointerEvents = typeof PointerEvent !== 'undefined' && (!defined(theNavigator.pointerEnabled) || theNavigator.pointerEnabled);
}
return hasPointerEvents;
}
var imageRenderingValueResult;
var supportsImageRenderingPixelatedResult;
function supportsImageRenderingPixelated() {
if (!defined(supportsImageRenderingPixelatedResult)) {
var canvas = document.createElement('canvas');
canvas.setAttribute('style',
'image-rendering: -moz-crisp-edges;' +
'image-rendering: pixelated;');
//canvas.style.imageRendering will be undefined, null or an empty string on unsupported browsers.
var tmp = canvas.style.imageRendering;
supportsImageRenderingPixelatedResult = defined(tmp) && tmp !== '';
if (supportsImageRenderingPixelatedResult) {
imageRenderingValueResult = tmp;
}
}
return supportsImageRenderingPixelatedResult;
}
function imageRenderingValue() {
return supportsImageRenderingPixelated() ? imageRenderingValueResult : undefined;
}
/**
* A set of functions to detect whether the current browser supports
* various features.
*
* @exports FeatureDetection
*/
var FeatureDetection = {
isChrome : isChrome,
chromeVersion : chromeVersion,
isSafari : isSafari,
safariVersion : safariVersion,
isWebkit : isWebkit,
webkitVersion : webkitVersion,
isInternetExplorer : isInternetExplorer,
internetExplorerVersion : internetExplorerVersion,
isEdge : isEdge,
edgeVersion : edgeVersion,
isFirefox : isFirefox,
firefoxVersion : firefoxVersion,
isWindows : isWindows,
hardwareConcurrency : defaultValue(theNavigator.hardwareConcurrency, 3),
supportsPointerEvents : supportsPointerEvents,
supportsImageRenderingPixelated: supportsImageRenderingPixelated,
imageRenderingValue: imageRenderingValue
};
/**
* Detects whether the current browser supports the full screen standard.
*
* @returns {Boolean} true if the browser supports the full screen standard, false if not.
*
* @see Fullscreen
* @see {@link http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html|W3C Fullscreen Living Specification}
*/
FeatureDetection.supportsFullscreen = function() {
return Fullscreen.supportsFullscreen();
};
/**
* Detects whether the current browser supports typed arrays.
*
* @returns {Boolean} true if the browser supports typed arrays, false if not.
*
* @see {@link http://www.khronos.org/registry/typedarray/specs/latest/|Typed Array Specification}
*/
FeatureDetection.supportsTypedArrays = function() {
return typeof ArrayBuffer !== 'undefined';
};
/**
* Detects whether the current browser supports Web Workers.
*
* @returns {Boolean} true if the browsers supports Web Workers, false if not.
*
* @see {@link http://www.w3.org/TR/workers/}
*/
FeatureDetection.supportsWebWorkers = function() {
return typeof Worker !== 'undefined';
};
return FeatureDetection;
});
/*global define*/
define('Core/Quaternion',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./FeatureDetection',
'./freezeObject',
'./Math',
'./Matrix3'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
FeatureDetection,
freezeObject,
CesiumMath,
Matrix3) {
'use strict';
/**
* A set of 4-dimensional coordinates used to represent rotation in 3-dimensional space.
* @alias Quaternion
* @constructor
*
* @param {Number} [x=0.0] The X component.
* @param {Number} [y=0.0] The Y component.
* @param {Number} [z=0.0] The Z component.
* @param {Number} [w=0.0] The W component.
*
* @see PackableForInterpolation
*/
function Quaternion(x, y, z, w) {
/**
* The X component.
* @type {Number}
* @default 0.0
*/
this.x = defaultValue(x, 0.0);
/**
* The Y component.
* @type {Number}
* @default 0.0
*/
this.y = defaultValue(y, 0.0);
/**
* The Z component.
* @type {Number}
* @default 0.0
*/
this.z = defaultValue(z, 0.0);
/**
* The W component.
* @type {Number}
* @default 0.0
*/
this.w = defaultValue(w, 0.0);
}
var fromAxisAngleScratch = new Cartesian3();
/**
* Computes a quaternion representing a rotation around an axis.
*
* @param {Cartesian3} axis The axis of rotation.
* @param {Number} angle The angle in radians to rotate around the axis.
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
*/
Quaternion.fromAxisAngle = function(axis, angle, result) {
if (!defined(axis)) {
throw new DeveloperError('axis is required.');
}
if (typeof angle !== 'number') {
throw new DeveloperError('angle is required and must be a number.');
}
var halfAngle = angle / 2.0;
var s = Math.sin(halfAngle);
fromAxisAngleScratch = Cartesian3.normalize(axis, fromAxisAngleScratch);
var x = fromAxisAngleScratch.x * s;
var y = fromAxisAngleScratch.y * s;
var z = fromAxisAngleScratch.z * s;
var w = Math.cos(halfAngle);
if (!defined(result)) {
return new Quaternion(x, y, z, w);
}
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
var fromRotationMatrixNext = [1, 2, 0];
var fromRotationMatrixQuat = new Array(3);
/**
* Computes a Quaternion from the provided Matrix3 instance.
*
* @param {Matrix3} matrix The rotation matrix.
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
*
* @see Matrix3.fromQuaternion
*/
Quaternion.fromRotationMatrix = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
var root;
var x;
var y;
var z;
var w;
var m00 = matrix[Matrix3.COLUMN0ROW0];
var m11 = matrix[Matrix3.COLUMN1ROW1];
var m22 = matrix[Matrix3.COLUMN2ROW2];
var trace = m00 + m11 + m22;
if (trace > 0.0) {
// |w| > 1/2, may as well choose w > 1/2
root = Math.sqrt(trace + 1.0); // 2w
w = 0.5 * root;
root = 0.5 / root; // 1/(4w)
x = (matrix[Matrix3.COLUMN1ROW2] - matrix[Matrix3.COLUMN2ROW1]) * root;
y = (matrix[Matrix3.COLUMN2ROW0] - matrix[Matrix3.COLUMN0ROW2]) * root;
z = (matrix[Matrix3.COLUMN0ROW1] - matrix[Matrix3.COLUMN1ROW0]) * root;
} else {
// |w| <= 1/2
var next = fromRotationMatrixNext;
var i = 0;
if (m11 > m00) {
i = 1;
}
if (m22 > m00 && m22 > m11) {
i = 2;
}
var j = next[i];
var k = next[j];
root = Math.sqrt(matrix[Matrix3.getElementIndex(i, i)] - matrix[Matrix3.getElementIndex(j, j)] - matrix[Matrix3.getElementIndex(k, k)] + 1.0);
var quat = fromRotationMatrixQuat;
quat[i] = 0.5 * root;
root = 0.5 / root;
w = (matrix[Matrix3.getElementIndex(k, j)] - matrix[Matrix3.getElementIndex(j, k)]) * root;
quat[j] = (matrix[Matrix3.getElementIndex(j, i)] + matrix[Matrix3.getElementIndex(i, j)]) * root;
quat[k] = (matrix[Matrix3.getElementIndex(k, i)] + matrix[Matrix3.getElementIndex(i, k)]) * root;
x = -quat[0];
y = -quat[1];
z = -quat[2];
}
if (!defined(result)) {
return new Quaternion(x, y, z, w);
}
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
var scratchHPRQuaternion = new Quaternion();
/**
* Computes a rotation from the given heading, pitch and roll angles. Heading is the rotation about the
* negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about
* the positive x axis.
*
* @param {Number} heading The heading angle in radians.
* @param {Number} pitch The pitch angle in radians.
* @param {Number} roll The roll angle in radians.
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if none was provided.
*/
Quaternion.fromHeadingPitchRoll = function(heading, pitch, roll, result) {
if (!defined(heading)) {
throw new DeveloperError('heading is required.');
}
if (!defined(pitch)) {
throw new DeveloperError('pitch is required.');
}
if (!defined(roll)) {
throw new DeveloperError('roll is required.');
}
var rollQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_X, roll, scratchHPRQuaternion);
var pitchQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -pitch, result);
result = Quaternion.multiply(pitchQuaternion, rollQuaternion, pitchQuaternion);
var headingQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -heading, scratchHPRQuaternion);
return Quaternion.multiply(headingQuaternion, result, result);
};
var sampledQuaternionAxis = new Cartesian3();
var sampledQuaternionRotation = new Cartesian3();
var sampledQuaternionTempQuaternion = new Quaternion();
var sampledQuaternionQuaternion0 = new Quaternion();
var sampledQuaternionQuaternion0Conjugate = new Quaternion();
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Quaternion.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {Quaternion} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Quaternion.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.x;
array[startingIndex++] = value.y;
array[startingIndex++] = value.z;
array[startingIndex] = value.w;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Quaternion} [result] The object into which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
*/
Quaternion.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Quaternion();
}
result.x = array[startingIndex];
result.y = array[startingIndex + 1];
result.z = array[startingIndex + 2];
result.w = array[startingIndex + 3];
return result;
};
/**
* The number of elements used to store the object into an array in its interpolatable form.
* @type {Number}
*/
Quaternion.packedInterpolationLength = 3;
/**
* Converts a packed array into a form suitable for interpolation.
*
* @param {Number[]} packedArray The packed array.
* @param {Number} [startingIndex=0] The index of the first element to be converted.
* @param {Number} [lastIndex=packedArray.length] The index of the last element to be converted.
* @param {Number[]} result The object into which to store the result.
*/
Quaternion.convertPackedArrayForInterpolation = function(packedArray, startingIndex, lastIndex, result) {
Quaternion.unpack(packedArray, lastIndex * 4, sampledQuaternionQuaternion0Conjugate);
Quaternion.conjugate(sampledQuaternionQuaternion0Conjugate, sampledQuaternionQuaternion0Conjugate);
for (var i = 0, len = lastIndex - startingIndex + 1; i < len; i++) {
var offset = i * 3;
Quaternion.unpack(packedArray, (startingIndex + i) * 4, sampledQuaternionTempQuaternion);
Quaternion.multiply(sampledQuaternionTempQuaternion, sampledQuaternionQuaternion0Conjugate, sampledQuaternionTempQuaternion);
if (sampledQuaternionTempQuaternion.w < 0) {
Quaternion.negate(sampledQuaternionTempQuaternion, sampledQuaternionTempQuaternion);
}
Quaternion.computeAxis(sampledQuaternionTempQuaternion, sampledQuaternionAxis);
var angle = Quaternion.computeAngle(sampledQuaternionTempQuaternion);
result[offset] = sampledQuaternionAxis.x * angle;
result[offset + 1] = sampledQuaternionAxis.y * angle;
result[offset + 2] = sampledQuaternionAxis.z * angle;
}
};
/**
* Retrieves an instance from a packed array converted with {@link convertPackedArrayForInterpolation}.
*
* @param {Number[]} array The array previously packed for interpolation.
* @param {Number[]} sourceArray The original packed array.
* @param {Number} [startingIndex=0] The startingIndex used to convert the array.
* @param {Number} [lastIndex=packedArray.length] The lastIndex used to convert the array.
* @param {Quaternion} [result] The object into which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
*/
Quaternion.unpackInterpolationResult = function(array, sourceArray, firstIndex, lastIndex, result) {
if (!defined(result)) {
result = new Quaternion();
}
Cartesian3.fromArray(array, 0, sampledQuaternionRotation);
var magnitude = Cartesian3.magnitude(sampledQuaternionRotation);
Quaternion.unpack(sourceArray, lastIndex * 4, sampledQuaternionQuaternion0);
if (magnitude === 0) {
Quaternion.clone(Quaternion.IDENTITY, sampledQuaternionTempQuaternion);
} else {
Quaternion.fromAxisAngle(sampledQuaternionRotation, magnitude, sampledQuaternionTempQuaternion);
}
return Quaternion.multiply(sampledQuaternionTempQuaternion, sampledQuaternionQuaternion0, result);
};
/**
* Duplicates a Quaternion instance.
*
* @param {Quaternion} quaternion The quaternion to duplicate.
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided. (Returns undefined if quaternion is undefined)
*/
Quaternion.clone = function(quaternion, result) {
if (!defined(quaternion)) {
return undefined;
}
if (!defined(result)) {
return new Quaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
}
result.x = quaternion.x;
result.y = quaternion.y;
result.z = quaternion.z;
result.w = quaternion.w;
return result;
};
/**
* Computes the conjugate of the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to conjugate.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.conjugate = function(quaternion, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = -quaternion.x;
result.y = -quaternion.y;
result.z = -quaternion.z;
result.w = quaternion.w;
return result;
};
/**
* Computes magnitude squared for the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to conjugate.
* @returns {Number} The magnitude squared.
*/
Quaternion.magnitudeSquared = function(quaternion) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
return quaternion.x * quaternion.x + quaternion.y * quaternion.y + quaternion.z * quaternion.z + quaternion.w * quaternion.w;
};
/**
* Computes magnitude for the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to conjugate.
* @returns {Number} The magnitude.
*/
Quaternion.magnitude = function(quaternion) {
return Math.sqrt(Quaternion.magnitudeSquared(quaternion));
};
/**
* Computes the normalized form of the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to normalize.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.normalize = function(quaternion, result) {
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var inverseMagnitude = 1.0 / Quaternion.magnitude(quaternion);
var x = quaternion.x * inverseMagnitude;
var y = quaternion.y * inverseMagnitude;
var z = quaternion.z * inverseMagnitude;
var w = quaternion.w * inverseMagnitude;
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
/**
* Computes the inverse of the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to normalize.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.inverse = function(quaternion, result) {
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var magnitudeSquared = Quaternion.magnitudeSquared(quaternion);
result = Quaternion.conjugate(quaternion, result);
return Quaternion.multiplyByScalar(result, 1.0 / magnitudeSquared, result);
};
/**
* Computes the componentwise sum of two quaternions.
*
* @param {Quaternion} left The first quaternion.
* @param {Quaternion} right The second quaternion.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x + right.x;
result.y = left.y + right.y;
result.z = left.z + right.z;
result.w = left.w + right.w;
return result;
};
/**
* Computes the componentwise difference of two quaternions.
*
* @param {Quaternion} left The first quaternion.
* @param {Quaternion} right The second quaternion.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = left.x - right.x;
result.y = left.y - right.y;
result.z = left.z - right.z;
result.w = left.w - right.w;
return result;
};
/**
* Negates the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to be negated.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.negate = function(quaternion, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = -quaternion.x;
result.y = -quaternion.y;
result.z = -quaternion.z;
result.w = -quaternion.w;
return result;
};
/**
* Computes the dot (scalar) product of two quaternions.
*
* @param {Quaternion} left The first quaternion.
* @param {Quaternion} right The second quaternion.
* @returns {Number} The dot product.
*/
Quaternion.dot = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
return left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w;
};
/**
* Computes the product of two quaternions.
*
* @param {Quaternion} left The first quaternion.
* @param {Quaternion} right The second quaternion.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.multiply = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var leftX = left.x;
var leftY = left.y;
var leftZ = left.z;
var leftW = left.w;
var rightX = right.x;
var rightY = right.y;
var rightZ = right.z;
var rightW = right.w;
var x = leftW * rightX + leftX * rightW + leftY * rightZ - leftZ * rightY;
var y = leftW * rightY - leftX * rightZ + leftY * rightW + leftZ * rightX;
var z = leftW * rightZ + leftX * rightY - leftY * rightX + leftZ * rightW;
var w = leftW * rightW - leftX * rightX - leftY * rightY - leftZ * rightZ;
result.x = x;
result.y = y;
result.z = z;
result.w = w;
return result;
};
/**
* Multiplies the provided quaternion componentwise by the provided scalar.
*
* @param {Quaternion} quaternion The quaternion to be scaled.
* @param {Number} scalar The scalar to multiply with.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.multiplyByScalar = function(quaternion, scalar, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = quaternion.x * scalar;
result.y = quaternion.y * scalar;
result.z = quaternion.z * scalar;
result.w = quaternion.w * scalar;
return result;
};
/**
* Divides the provided quaternion componentwise by the provided scalar.
*
* @param {Quaternion} quaternion The quaternion to be divided.
* @param {Number} scalar The scalar to divide by.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.divideByScalar = function(quaternion, scalar, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = quaternion.x / scalar;
result.y = quaternion.y / scalar;
result.z = quaternion.z / scalar;
result.w = quaternion.w / scalar;
return result;
};
/**
* Computes the axis of rotation of the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to use.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Quaternion.computeAxis = function(quaternion, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var w = quaternion.w;
if (Math.abs(w - 1.0) < CesiumMath.EPSILON6) {
result.x = result.y = result.z = 0;
return result;
}
var scalar = 1.0 / Math.sqrt(1.0 - (w * w));
result.x = quaternion.x * scalar;
result.y = quaternion.y * scalar;
result.z = quaternion.z * scalar;
return result;
};
/**
* Computes the angle of rotation of the provided quaternion.
*
* @param {Quaternion} quaternion The quaternion to use.
* @returns {Number} The angle of rotation.
*/
Quaternion.computeAngle = function(quaternion) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required');
}
if (Math.abs(quaternion.w - 1.0) < CesiumMath.EPSILON6) {
return 0.0;
}
return 2.0 * Math.acos(quaternion.w);
};
var lerpScratch = new Quaternion();
/**
* Computes the linear interpolation or extrapolation at t using the provided quaternions.
*
* @param {Quaternion} start The value corresponding to t at 0.0.
* @param {Quaternion} end The value corresponding to t at 1.0.
* @param {Number} t The point along t at which to interpolate.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.lerp = function(start, end, t, result) {
if (!defined(start)) {
throw new DeveloperError('start is required.');
}
if (!defined(end)) {
throw new DeveloperError('end is required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
lerpScratch = Quaternion.multiplyByScalar(end, t, lerpScratch);
result = Quaternion.multiplyByScalar(start, 1.0 - t, result);
return Quaternion.add(lerpScratch, result, result);
};
var slerpEndNegated = new Quaternion();
var slerpScaledP = new Quaternion();
var slerpScaledR = new Quaternion();
/**
* Computes the spherical linear interpolation or extrapolation at t using the provided quaternions.
*
* @param {Quaternion} start The value corresponding to t at 0.0.
* @param {Quaternion} end The value corresponding to t at 1.0.
* @param {Number} t The point along t at which to interpolate.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*
* @see Quaternion#fastSlerp
*/
Quaternion.slerp = function(start, end, t, result) {
if (!defined(start)) {
throw new DeveloperError('start is required.');
}
if (!defined(end)) {
throw new DeveloperError('end is required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var dot = Quaternion.dot(start, end);
// The angle between start must be acute. Since q and -q represent
// the same rotation, negate q to get the acute angle.
var r = end;
if (dot < 0.0) {
dot = -dot;
r = slerpEndNegated = Quaternion.negate(end, slerpEndNegated);
}
// dot > 0, as the dot product approaches 1, the angle between the
// quaternions vanishes. use linear interpolation.
if (1.0 - dot < CesiumMath.EPSILON6) {
return Quaternion.lerp(start, r, t, result);
}
var theta = Math.acos(dot);
slerpScaledP = Quaternion.multiplyByScalar(start, Math.sin((1 - t) * theta), slerpScaledP);
slerpScaledR = Quaternion.multiplyByScalar(r, Math.sin(t * theta), slerpScaledR);
result = Quaternion.add(slerpScaledP, slerpScaledR, result);
return Quaternion.multiplyByScalar(result, 1.0 / Math.sin(theta), result);
};
/**
* The logarithmic quaternion function.
*
* @param {Quaternion} quaternion The unit quaternion.
* @param {Cartesian3} result The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter.
*/
Quaternion.log = function(quaternion, result) {
if (!defined(quaternion)) {
throw new DeveloperError('quaternion is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var theta = CesiumMath.acosClamped(quaternion.w);
var thetaOverSinTheta = 0.0;
if (theta !== 0.0) {
thetaOverSinTheta = theta / Math.sin(theta);
}
return Cartesian3.multiplyByScalar(quaternion, thetaOverSinTheta, result);
};
/**
* The exponential quaternion function.
*
* @param {Cartesian3} cartesian The cartesian.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*/
Quaternion.exp = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var theta = Cartesian3.magnitude(cartesian);
var sinThetaOverTheta = 0.0;
if (theta !== 0.0) {
sinThetaOverTheta = Math.sin(theta) / theta;
}
result.x = cartesian.x * sinThetaOverTheta;
result.y = cartesian.y * sinThetaOverTheta;
result.z = cartesian.z * sinThetaOverTheta;
result.w = Math.cos(theta);
return result;
};
var squadScratchCartesian0 = new Cartesian3();
var squadScratchCartesian1 = new Cartesian3();
var squadScratchQuaternion0 = new Quaternion();
var squadScratchQuaternion1 = new Quaternion();
/**
* Computes an inner quadrangle point.
* This will compute quaternions that ensure a squad curve is C1 .
*
* @param {Quaternion} q0 The first quaternion.
* @param {Quaternion} q1 The second quaternion.
* @param {Quaternion} q2 The third quaternion.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*
* @see Quaternion#squad
*/
Quaternion.computeInnerQuadrangle = function(q0, q1, q2, result) {
if (!defined(q0) || !defined(q1) || !defined(q2)) {
throw new DeveloperError('q0, q1, and q2 are required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var qInv = Quaternion.conjugate(q1, squadScratchQuaternion0);
Quaternion.multiply(qInv, q2, squadScratchQuaternion1);
var cart0 = Quaternion.log(squadScratchQuaternion1, squadScratchCartesian0);
Quaternion.multiply(qInv, q0, squadScratchQuaternion1);
var cart1 = Quaternion.log(squadScratchQuaternion1, squadScratchCartesian1);
Cartesian3.add(cart0, cart1, cart0);
Cartesian3.multiplyByScalar(cart0, 0.25, cart0);
Cartesian3.negate(cart0, cart0);
Quaternion.exp(cart0, squadScratchQuaternion0);
return Quaternion.multiply(q1, squadScratchQuaternion0, result);
};
/**
* Computes the spherical quadrangle interpolation between quaternions.
*
* @param {Quaternion} q0 The first quaternion.
* @param {Quaternion} q1 The second quaternion.
* @param {Quaternion} s0 The first inner quadrangle.
* @param {Quaternion} s1 The second inner quadrangle.
* @param {Number} t The time in [0,1] used to interpolate.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*
*
* @example
* // 1. compute the squad interpolation between two quaternions on a curve
* var s0 = Cesium.Quaternion.computeInnerQuadrangle(quaternions[i - 1], quaternions[i], quaternions[i + 1], new Cesium.Quaternion());
* var s1 = Cesium.Quaternion.computeInnerQuadrangle(quaternions[i], quaternions[i + 1], quaternions[i + 2], new Cesium.Quaternion());
* var q = Cesium.Quaternion.squad(quaternions[i], quaternions[i + 1], s0, s1, t, new Cesium.Quaternion());
*
* // 2. compute the squad interpolation as above but where the first quaternion is a end point.
* var s1 = Cesium.Quaternion.computeInnerQuadrangle(quaternions[0], quaternions[1], quaternions[2], new Cesium.Quaternion());
* var q = Cesium.Quaternion.squad(quaternions[0], quaternions[1], quaternions[0], s1, t, new Cesium.Quaternion());
*
* @see Quaternion#computeInnerQuadrangle
*/
Quaternion.squad = function(q0, q1, s0, s1, t, result) {
if (!defined(q0) || !defined(q1) || !defined(s0) || !defined(s1)) {
throw new DeveloperError('q0, q1, s0, and s1 are required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var slerp0 = Quaternion.slerp(q0, q1, t, squadScratchQuaternion0);
var slerp1 = Quaternion.slerp(s0, s1, t, squadScratchQuaternion1);
return Quaternion.slerp(slerp0, slerp1, 2.0 * t * (1.0 - t), result);
};
var fastSlerpScratchQuaternion = new Quaternion();
var opmu = 1.90110745351730037;
var u = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
var v = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
var bT = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
var bD = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
for (var i = 0; i < 7; ++i) {
var s = i + 1.0;
var t = 2.0 * s + 1.0;
u[i] = 1.0 / (s * t);
v[i] = s / t;
}
u[7] = opmu / (8.0 * 17.0);
v[7] = opmu * 8.0 / 17.0;
/**
* Computes the spherical linear interpolation or extrapolation at t using the provided quaternions.
* This implementation is faster than {@link Quaternion#slerp}, but is only accurate up to 10-6 .
*
* @param {Quaternion} start The value corresponding to t at 0.0.
* @param {Quaternion} end The value corresponding to t at 1.0.
* @param {Number} t The point along t at which to interpolate.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter.
*
* @see Quaternion#slerp
*/
Quaternion.fastSlerp = function(start, end, t, result) {
if (!defined(start)) {
throw new DeveloperError('start is required.');
}
if (!defined(end)) {
throw new DeveloperError('end is required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var x = Quaternion.dot(start, end);
var sign;
if (x >= 0) {
sign = 1.0;
} else {
sign = -1.0;
x = -x;
}
var xm1 = x - 1.0;
var d = 1.0 - t;
var sqrT = t * t;
var sqrD = d * d;
for (var i = 7; i >= 0; --i) {
bT[i] = (u[i] * sqrT - v[i]) * xm1;
bD[i] = (u[i] * sqrD - v[i]) * xm1;
}
var cT = sign * t * (
1.0 + bT[0] * (1.0 + bT[1] * (1.0 + bT[2] * (1.0 + bT[3] * (
1.0 + bT[4] * (1.0 + bT[5] * (1.0 + bT[6] * (1.0 + bT[7]))))))));
var cD = d * (
1.0 + bD[0] * (1.0 + bD[1] * (1.0 + bD[2] * (1.0 + bD[3] * (
1.0 + bD[4] * (1.0 + bD[5] * (1.0 + bD[6] * (1.0 + bD[7]))))))));
var temp = Quaternion.multiplyByScalar(start, cD, fastSlerpScratchQuaternion);
Quaternion.multiplyByScalar(end, cT, result);
return Quaternion.add(temp, result, result);
};
/**
* Computes the spherical quadrangle interpolation between quaternions.
* An implementation that is faster than {@link Quaternion#squad}, but less accurate.
*
* @param {Quaternion} q0 The first quaternion.
* @param {Quaternion} q1 The second quaternion.
* @param {Quaternion} s0 The first inner quadrangle.
* @param {Quaternion} s1 The second inner quadrangle.
* @param {Number} t The time in [0,1] used to interpolate.
* @param {Quaternion} result The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new instance if none was provided.
*
* @see Quaternion#squad
*/
Quaternion.fastSquad = function(q0, q1, s0, s1, t, result) {
if (!defined(q0) || !defined(q1) || !defined(s0) || !defined(s1)) {
throw new DeveloperError('q0, q1, s0, and s1 are required.');
}
if (typeof t !== 'number') {
throw new DeveloperError('t is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var slerp0 = Quaternion.fastSlerp(q0, q1, t, squadScratchQuaternion0);
var slerp1 = Quaternion.fastSlerp(s0, s1, t, squadScratchQuaternion1);
return Quaternion.fastSlerp(slerp0, slerp1, 2.0 * t * (1.0 - t), result);
};
/**
* Compares the provided quaternions componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Quaternion} [left] The first quaternion.
* @param {Quaternion} [right] The second quaternion.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Quaternion.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.x === right.x) &&
(left.y === right.y) &&
(left.z === right.z) &&
(left.w === right.w));
};
/**
* Compares the provided quaternions componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Quaternion} [left] The first quaternion.
* @param {Quaternion} [right] The second quaternion.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Quaternion.equalsEpsilon = function(left, right, epsilon) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon is required and must be a number.');
}
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(Math.abs(left.x - right.x) <= epsilon) &&
(Math.abs(left.y - right.y) <= epsilon) &&
(Math.abs(left.z - right.z) <= epsilon) &&
(Math.abs(left.w - right.w) <= epsilon));
};
/**
* An immutable Quaternion instance initialized to (0.0, 0.0, 0.0, 0.0).
*
* @type {Quaternion}
* @constant
*/
Quaternion.ZERO = freezeObject(new Quaternion(0.0, 0.0, 0.0, 0.0));
/**
* An immutable Quaternion instance initialized to (0.0, 0.0, 0.0, 1.0).
*
* @type {Quaternion}
* @constant
*/
Quaternion.IDENTITY = freezeObject(new Quaternion(0.0, 0.0, 0.0, 1.0));
/**
* Duplicates this Quaternion instance.
*
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
*/
Quaternion.prototype.clone = function(result) {
return Quaternion.clone(this, result);
};
/**
* Compares this and the provided quaternion componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Quaternion} [right] The right hand side quaternion.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Quaternion.prototype.equals = function(right) {
return Quaternion.equals(this, right);
};
/**
* Compares this and the provided quaternion componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Quaternion} [right] The right hand side quaternion.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Quaternion.prototype.equalsEpsilon = function(right, epsilon) {
return Quaternion.equalsEpsilon(this, right, epsilon);
};
/**
* Returns a string representing this quaternion in the format (x, y, z, w).
*
* @returns {String} A string representing this Quaternion.
*/
Quaternion.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + ')';
};
return Quaternion;
});
/*global define*/
define('Core/Transforms',[
'../ThirdParty/when',
'./Cartesian2',
'./Cartesian3',
'./Cartesian4',
'./Cartographic',
'./defaultValue',
'./defined',
'./deprecationWarning',
'./DeveloperError',
'./EarthOrientationParameters',
'./EarthOrientationParametersSample',
'./Ellipsoid',
'./HeadingPitchRoll',
'./Iau2006XysData',
'./Iau2006XysSample',
'./JulianDate',
'./Math',
'./Matrix3',
'./Matrix4',
'./Quaternion',
'./TimeConstants'
], function(
when,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
defaultValue,
defined,
deprecationWarning,
DeveloperError,
EarthOrientationParameters,
EarthOrientationParametersSample,
Ellipsoid,
HeadingPitchRoll,
Iau2006XysData,
Iau2006XysSample,
JulianDate,
CesiumMath,
Matrix3,
Matrix4,
Quaternion,
TimeConstants) {
'use strict';
/**
* Contains functions for transforming positions to various reference frames.
*
* @exports Transforms
*/
var Transforms = {};
var eastNorthUpToFixedFrameNormal = new Cartesian3();
var eastNorthUpToFixedFrameTangent = new Cartesian3();
var eastNorthUpToFixedFrameBitangent = new Cartesian3();
/**
* Computes a 4x4 transformation matrix from a reference frame with an east-north-up axes
* centered at the provided origin to the provided ellipsoid's fixed reference frame.
* The local axes are defined as:
*
* The x
axis points in the local east direction.
* The y
axis points in the local north direction.
* The z
axis points in the direction of the ellipsoid surface normal which passes through the position.
*
*
* @param {Cartesian3} origin The center point of the local reference frame.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
*
* @example
* // Get the transform from local east-north-up at cartographic (0.0, 0.0) to Earth's fixed frame.
* var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
*/
Transforms.eastNorthUpToFixedFrame = function(origin, ellipsoid, result) {
if (!defined(origin)) {
throw new DeveloperError('origin is required.');
}
// If x and y are zero, assume origin is at a pole, which is a special case.
if (CesiumMath.equalsEpsilon(origin.x, 0.0, CesiumMath.EPSILON14) &&
CesiumMath.equalsEpsilon(origin.y, 0.0, CesiumMath.EPSILON14)) {
var sign = CesiumMath.sign(origin.z);
if (!defined(result)) {
return new Matrix4(
0.0, -sign, 0.0, origin.x,
1.0, 0.0, 0.0, origin.y,
0.0, 0.0, sign, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = 0.0;
result[1] = 1.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = -sign;
result[5] = 0.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = sign;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
}
var normal = eastNorthUpToFixedFrameNormal;
var tangent = eastNorthUpToFixedFrameTangent;
var bitangent = eastNorthUpToFixedFrameBitangent;
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
ellipsoid.geodeticSurfaceNormal(origin, normal);
tangent.x = -origin.y;
tangent.y = origin.x;
tangent.z = 0.0;
Cartesian3.normalize(tangent, tangent);
Cartesian3.cross(normal, tangent, bitangent);
if (!defined(result)) {
return new Matrix4(
tangent.x, bitangent.x, normal.x, origin.x,
tangent.y, bitangent.y, normal.y, origin.y,
tangent.z, bitangent.z, normal.z, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = tangent.x;
result[1] = tangent.y;
result[2] = tangent.z;
result[3] = 0.0;
result[4] = bitangent.x;
result[5] = bitangent.y;
result[6] = bitangent.z;
result[7] = 0.0;
result[8] = normal.x;
result[9] = normal.y;
result[10] = normal.z;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
};
var northEastDownToFixedFrameNormal = new Cartesian3();
var northEastDownToFixedFrameTangent = new Cartesian3();
var northEastDownToFixedFrameBitangent = new Cartesian3();
/**
* Computes a 4x4 transformation matrix from a reference frame with an north-east-down axes
* centered at the provided origin to the provided ellipsoid's fixed reference frame.
* The local axes are defined as:
*
* The x
axis points in the local north direction.
* The y
axis points in the local east direction.
* The z
axis points in the opposite direction of the ellipsoid surface normal which passes through the position.
*
*
* @param {Cartesian3} origin The center point of the local reference frame.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
*
* @example
* // Get the transform from local north-east-down at cartographic (0.0, 0.0) to Earth's fixed frame.
* var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var transform = Cesium.Transforms.northEastDownToFixedFrame(center);
*/
Transforms.northEastDownToFixedFrame = function(origin, ellipsoid, result) {
if (!defined(origin)) {
throw new DeveloperError('origin is required.');
}
if (CesiumMath.equalsEpsilon(origin.x, 0.0, CesiumMath.EPSILON14) &&
CesiumMath.equalsEpsilon(origin.y, 0.0, CesiumMath.EPSILON14)) {
// The poles are special cases. If x and y are zero, assume origin is at a pole.
var sign = CesiumMath.sign(origin.z);
if (!defined(result)) {
return new Matrix4(
-sign, 0.0, 0.0, origin.x,
0.0, 1.0, 0.0, origin.y,
0.0, 0.0, -sign, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = -sign;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = 1.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = -sign;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
}
var normal = northEastDownToFixedFrameNormal;
var tangent = northEastDownToFixedFrameTangent;
var bitangent = northEastDownToFixedFrameBitangent;
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
ellipsoid.geodeticSurfaceNormal(origin, normal);
tangent.x = -origin.y;
tangent.y = origin.x;
tangent.z = 0.0;
Cartesian3.normalize(tangent, tangent);
Cartesian3.cross(normal, tangent, bitangent);
if (!defined(result)) {
return new Matrix4(
bitangent.x, tangent.x, -normal.x, origin.x,
bitangent.y, tangent.y, -normal.y, origin.y,
bitangent.z, tangent.z, -normal.z, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = bitangent.x;
result[1] = bitangent.y;
result[2] = bitangent.z;
result[3] = 0.0;
result[4] = tangent.x;
result[5] = tangent.y;
result[6] = tangent.z;
result[7] = 0.0;
result[8] = -normal.x;
result[9] = -normal.y;
result[10] = -normal.z;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
};
/**
* Computes a 4x4 transformation matrix from a reference frame with an north-up-east axes
* centered at the provided origin to the provided ellipsoid's fixed reference frame.
* The local axes are defined as:
*
* The x
axis points in the local north direction.
* The y
axis points in the direction of the ellipsoid surface normal which passes through the position.
* The z
axis points in the local east direction.
*
*
* @param {Cartesian3} origin The center point of the local reference frame.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
*
* @example
* // Get the transform from local north-up-east at cartographic (0.0, 0.0) to Earth's fixed frame.
* var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var transform = Cesium.Transforms.northUpEastToFixedFrame(center);
*/
Transforms.northUpEastToFixedFrame = function(origin, ellipsoid, result) {
if (!defined(origin)) {
throw new DeveloperError('origin is required.');
}
// If x and y are zero, assume origin is at a pole, which is a special case.
if (CesiumMath.equalsEpsilon(origin.x, 0.0, CesiumMath.EPSILON14) &&
CesiumMath.equalsEpsilon(origin.y, 0.0, CesiumMath.EPSILON14)) {
var sign = CesiumMath.sign(origin.z);
if (!defined(result)) {
return new Matrix4(
-sign, 0.0, 0.0, origin.x,
0.0, 0.0, 1.0, origin.y,
0.0, sign, 0.0, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = -sign;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = 0.0;
result[6] = sign;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 1.0;
result[10] = 0.0;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
}
var normal = eastNorthUpToFixedFrameNormal;
var tangent = eastNorthUpToFixedFrameTangent;
var bitangent = eastNorthUpToFixedFrameBitangent;
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
ellipsoid.geodeticSurfaceNormal(origin, normal);
tangent.x = -origin.y;
tangent.y = origin.x;
tangent.z = 0.0;
Cartesian3.normalize(tangent, tangent);
Cartesian3.cross(normal, tangent, bitangent);
if (!defined(result)) {
return new Matrix4(
bitangent.x, normal.x, tangent.x, origin.x,
bitangent.y, normal.y, tangent.y, origin.y,
bitangent.z, normal.z, tangent.z, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = bitangent.x;
result[1] = bitangent.y;
result[2] = bitangent.z;
result[3] = 0.0;
result[4] = normal.x;
result[5] = normal.y;
result[6] = normal.z;
result[7] = 0.0;
result[8] = tangent.x;
result[9] = tangent.y;
result[10] = tangent.z;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
};
/**
* Computes a 4x4 transformation matrix from a reference frame with an north-west-up axes
* centered at the provided origin to the provided ellipsoid's fixed reference frame.
* The local axes are defined as:
*
* The x
axis points in the local north direction.
* The y
axis points in the local west direction.
* The z
axis points in the direction of the ellipsoid surface normal which passes through the position.
*
*
* @param {Cartesian3} origin The center point of the local reference frame.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
*
* @example
* // Get the transform from local north-West-Up at cartographic (0.0, 0.0) to Earth's fixed frame.
* var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var transform = Cesium.Transforms.northWestUpToFixedFrame(center);
*/
Transforms.northWestUpToFixedFrame = function(origin, ellipsoid, result) {
if (!defined(origin)) {
throw new DeveloperError('origin is required.');
}
// If x and y are zero, assume origin is at a pole, which is a special case.
if (CesiumMath.equalsEpsilon(origin.x, 0.0, CesiumMath.EPSILON14) &&
CesiumMath.equalsEpsilon(origin.y, 0.0, CesiumMath.EPSILON14)) {
var sign = CesiumMath.sign(origin.z);
if (!defined(result)) {
return new Matrix4(
-sign, 0.0, 0.0, origin.x,
0.0, -1.0, 0.0, origin.y,
0.0, 0.0, sign, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = -sign;
result[1] = 0.0;
result[2] = 0.0;
result[3] = 0.0;
result[4] = 0.0;
result[5] = -1.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 0.0;
result[9] = 0.0;
result[10] = sign;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
}
var normal = eastNorthUpToFixedFrameNormal;//Up
var tangent = eastNorthUpToFixedFrameTangent;//East
var bitangent = eastNorthUpToFixedFrameBitangent;//North
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
ellipsoid.geodeticSurfaceNormal(origin, normal);
tangent.x = -origin.y;
tangent.y = origin.x;
tangent.z = 0.0;
Cartesian3.normalize(tangent, tangent);
Cartesian3.cross(normal, tangent, bitangent);
if (!defined(result)) {
return new Matrix4(
bitangent.x, -tangent.x, normal.x, origin.x,
bitangent.y, -tangent.y, normal.y, origin.y,
bitangent.z, -tangent.z, normal.z, origin.z,
0.0, 0.0, 0.0, 1.0);
}
result[0] = bitangent.x;
result[1] = bitangent.y;
result[2] = bitangent.z;
result[3] = 0.0;
result[4] = -tangent.x;
result[5] = -tangent.y;
result[6] = -tangent.z;
result[7] = 0.0;
result[8] = normal.x;
result[9] = normal.y;
result[10] = normal.z;
result[11] = 0.0;
result[12] = origin.x;
result[13] = origin.y;
result[14] = origin.z;
result[15] = 1.0;
return result;
};
var scratchHPRQuaternion = new Quaternion();
var scratchScale = new Cartesian3(1.0, 1.0, 1.0);
var scratchHPRMatrix4 = new Matrix4();
/**
* Computes a 4x4 transformation matrix from a reference frame with axes computed from the heading-pitch-roll angles
* centered at the provided origin to the provided ellipsoid's fixed reference frame. Heading is the rotation from the local north
* direction where a positive angle is increasing eastward. Pitch is the rotation from the local east-north plane. Positive pitch angles
* are above the plane. Negative pitch angles are below the plane. Roll is the first rotation applied about the local east axis.
*
* @param {Cartesian3} origin The center point of the local reference frame.
* @param {HeadingPitchRoll} headingPitchRoll The heading, pitch, and roll.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
* @param {Matrix4} [result] The object onto which to store the result.
* @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
*
* @example
* // Get the transform from local heading-pitch-roll at cartographic (0.0, 0.0) to Earth's fixed frame.
* var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var heading = -Cesium.Math.PI_OVER_TWO;
* var pitch = Cesium.Math.PI_OVER_FOUR;
* var roll = 0.0;
* var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
* var transform = Cesium.Transforms.headingPitchRollToFixedFrame(center, hpr);
*/
Transforms.headingPitchRollToFixedFrame = function(origin, headingPitchRoll, pitch, roll, ellipsoid, result) {
var heading;
if (typeof headingPitchRoll === 'object') {
// Shift arguments using assignments to encourage JIT optimization.
ellipsoid = pitch;
result = roll;
heading = headingPitchRoll.heading;
pitch = headingPitchRoll.pitch;
roll = headingPitchRoll.roll;
} else {
deprecationWarning('headingPitchRollToFixedFrame', 'headingPitchRollToFixedFrame with separate heading, pitch, and roll arguments was deprecated in 1.27. It will be removed in 1.30. Use a HeadingPitchRoll object.');
heading = headingPitchRoll;
}
// checks for required parameters happen in the called functions
var hprQuaternion = Quaternion.fromHeadingPitchRoll(heading, pitch, roll, scratchHPRQuaternion);
var hprMatrix = Matrix4.fromTranslationQuaternionRotationScale(Cartesian3.ZERO, hprQuaternion, scratchScale, scratchHPRMatrix4);
result = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid, result);
return Matrix4.multiply(result, hprMatrix, result);
};
var scratchHPR = new HeadingPitchRoll();
var scratchENUMatrix4 = new Matrix4();
var scratchHPRMatrix3 = new Matrix3();
/**
* Computes a quaternion from a reference frame with axes computed from the heading-pitch-roll angles
* centered at the provided origin. Heading is the rotation from the local north
* direction where a positive angle is increasing eastward. Pitch is the rotation from the local east-north plane. Positive pitch angles
* are above the plane. Negative pitch angles are below the plane. Roll is the first rotation applied about the local east axis.
*
* @param {Cartesian3} origin The center point of the local reference frame.
* @param {HeadingPitchRoll} headingPitchRoll The heading, pitch, and roll.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new Quaternion instance if none was provided.
*
* @example
* // Get the quaternion from local heading-pitch-roll at cartographic (0.0, 0.0) to Earth's fixed frame.
* var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var heading = -Cesium.Math.PI_OVER_TWO;
* var pitch = Cesium.Math.PI_OVER_FOUR;
* var roll = 0.0;
* var hpr = new HeadingPitchRoll(heading, pitch, roll);
* var quaternion = Cesium.Transforms.headingPitchRollQuaternion(center, hpr);
*/
Transforms.headingPitchRollQuaternion = function(origin, headingPitchRoll, pitch, roll, ellipsoid, result) {
var hpr;
if (typeof headingPitchRoll === 'object') {
// Shift arguments using assignment to encourage JIT optimization.
hpr = headingPitchRoll;
ellipsoid = pitch;
result = roll;
} else {
deprecationWarning('headingPitchRollQuaternion', 'headingPitchRollQuaternion with separate heading, pitch, and roll arguments was deprecated in 1.27. It will be removed in 1.30. Use a HeadingPitchRoll object.');
scratchHPR.heading = headingPitchRoll;
scratchHPR.pitch = pitch;
scratchHPR.roll = roll;
hpr = scratchHPR;
}
// checks for required parameters happen in the called functions
var transform = Transforms.headingPitchRollToFixedFrame(origin, hpr, ellipsoid, scratchENUMatrix4);
var rotation = Matrix4.getRotation(transform, scratchHPRMatrix3);
return Quaternion.fromRotationMatrix(rotation, result);
};
var gmstConstant0 = 6 * 3600 + 41 * 60 + 50.54841;
var gmstConstant1 = 8640184.812866;
var gmstConstant2 = 0.093104;
var gmstConstant3 = -6.2E-6;
var rateCoef = 1.1772758384668e-19;
var wgs84WRPrecessing = 7.2921158553E-5;
var twoPiOverSecondsInDay = CesiumMath.TWO_PI / 86400.0;
var dateInUtc = new JulianDate();
/**
* Computes a rotation matrix to transform a point or vector from True Equator Mean Equinox (TEME) axes to the
* pseudo-fixed axes at a given time. This method treats the UT1 time standard as equivalent to UTC.
*
* @param {JulianDate} date The time at which to compute the rotation matrix.
* @param {Matrix3} [result] The object onto which to store the result.
* @returns {Matrix3} The modified result parameter or a new Matrix3 instance if none was provided.
*
* @example
* //Set the view to in the inertial frame.
* scene.preRender.addEventListener(function(scene, time) {
* var now = Cesium.JulianDate.now();
* var offset = Cesium.Matrix4.multiplyByPoint(camera.transform, camera.position, new Cesium.Cartesian3());
* var transform = Cesium.Matrix4.fromRotationTranslation(Cesium.Transforms.computeTemeToPseudoFixedMatrix(now));
* var inverseTransform = Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4());
* Cesium.Matrix4.multiplyByPoint(inverseTransform, offset, offset);
* camera.lookAtTransform(transform, offset);
* });
*/
Transforms.computeTemeToPseudoFixedMatrix = function (date, result) {
if (!defined(date)) {
throw new DeveloperError('date is required.');
}
// GMST is actually computed using UT1. We're using UTC as an approximation of UT1.
// We do not want to use the function like convertTaiToUtc in JulianDate because
// we explicitly do not want to fail when inside the leap second.
dateInUtc = JulianDate.addSeconds(date, -JulianDate.computeTaiMinusUtc(date), dateInUtc);
var utcDayNumber = dateInUtc.dayNumber;
var utcSecondsIntoDay = dateInUtc.secondsOfDay;
var t;
var diffDays = utcDayNumber - 2451545;
if (utcSecondsIntoDay >= 43200.0) {
t = (diffDays + 0.5) / TimeConstants.DAYS_PER_JULIAN_CENTURY;
} else {
t = (diffDays - 0.5) / TimeConstants.DAYS_PER_JULIAN_CENTURY;
}
var gmst0 = gmstConstant0 + t * (gmstConstant1 + t * (gmstConstant2 + t * gmstConstant3));
var angle = (gmst0 * twoPiOverSecondsInDay) % CesiumMath.TWO_PI;
var ratio = wgs84WRPrecessing + rateCoef * (utcDayNumber - 2451545.5);
var secondsSinceMidnight = (utcSecondsIntoDay + TimeConstants.SECONDS_PER_DAY * 0.5) % TimeConstants.SECONDS_PER_DAY;
var gha = angle + (ratio * secondsSinceMidnight);
var cosGha = Math.cos(gha);
var sinGha = Math.sin(gha);
if (!defined(result)) {
return new Matrix3(cosGha, sinGha, 0.0,
-sinGha, cosGha, 0.0,
0.0, 0.0, 1.0);
}
result[0] = cosGha;
result[1] = -sinGha;
result[2] = 0.0;
result[3] = sinGha;
result[4] = cosGha;
result[5] = 0.0;
result[6] = 0.0;
result[7] = 0.0;
result[8] = 1.0;
return result;
};
/**
* The source of IAU 2006 XYS data, used for computing the transformation between the
* Fixed and ICRF axes.
* @type {Iau2006XysData}
*
* @see Transforms.computeIcrfToFixedMatrix
* @see Transforms.computeFixedToIcrfMatrix
*
* @private
*/
Transforms.iau2006XysData = new Iau2006XysData();
/**
* The source of Earth Orientation Parameters (EOP) data, used for computing the transformation
* between the Fixed and ICRF axes. By default, zero values are used for all EOP values,
* yielding a reasonable but not completely accurate representation of the ICRF axes.
* @type {EarthOrientationParameters}
*
* @see Transforms.computeIcrfToFixedMatrix
* @see Transforms.computeFixedToIcrfMatrix
*
* @private
*/
Transforms.earthOrientationParameters = EarthOrientationParameters.NONE;
var ttMinusTai = 32.184;
var j2000ttDays = 2451545.0;
/**
* Preloads the data necessary to transform between the ICRF and Fixed axes, in either
* direction, over a given interval. This function returns a promise that, when resolved,
* indicates that the preload has completed.
*
* @param {TimeInterval} timeInterval The interval to preload.
* @returns {Promise.} A promise that, when resolved, indicates that the preload has completed
* and evaluation of the transformation between the fixed and ICRF axes will
* no longer return undefined for a time inside the interval.
*
*
* @example
* var interval = new Cesium.TimeInterval(...);
* when(Cesium.Transforms.preloadIcrfFixed(interval), function() {
* // the data is now loaded
* });
*
* @see Transforms.computeIcrfToFixedMatrix
* @see Transforms.computeFixedToIcrfMatrix
* @see when
*/
Transforms.preloadIcrfFixed = function(timeInterval) {
var startDayTT = timeInterval.start.dayNumber;
var startSecondTT = timeInterval.start.secondsOfDay + ttMinusTai;
var stopDayTT = timeInterval.stop.dayNumber;
var stopSecondTT = timeInterval.stop.secondsOfDay + ttMinusTai;
var xysPromise = Transforms.iau2006XysData.preload(startDayTT, startSecondTT, stopDayTT, stopSecondTT);
var eopPromise = Transforms.earthOrientationParameters.getPromiseToLoad();
return when.all([xysPromise, eopPromise]);
};
/**
* Computes a rotation matrix to transform a point or vector from the International Celestial
* Reference Frame (GCRF/ICRF) inertial frame axes to the Earth-Fixed frame axes (ITRF)
* at a given time. This function may return undefined if the data necessary to
* do the transformation is not yet loaded.
*
* @param {JulianDate} date The time at which to compute the rotation matrix.
* @param {Matrix3} [result] The object onto which to store the result. If this parameter is
* not specified, a new instance is created and returned.
* @returns {Matrix3} The rotation matrix, or undefined if the data necessary to do the
* transformation is not yet loaded.
*
*
* @example
* scene.preRender.addEventListener(function(scene, time) {
* var icrfToFixed = Cesium.Transforms.computeIcrfToFixedMatrix(time);
* if (Cesium.defined(icrfToFixed)) {
* var offset = Cesium.Matrix4.multiplyByPoint(camera.transform, camera.position, new Cesium.Cartesian3());
* var transform = Cesium.Matrix4.fromRotationTranslation(icrfToFixed)
* var inverseTransform = Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4());
* Cesium.Matrix4.multiplyByPoint(inverseTransform, offset, offset);
* camera.lookAtTransform(transform, offset);
* }
* });
*
* @see Transforms.preloadIcrfFixed
*/
Transforms.computeIcrfToFixedMatrix = function(date, result) {
if (!defined(date)) {
throw new DeveloperError('date is required.');
}
if (!defined(result)) {
result = new Matrix3();
}
var fixedToIcrfMtx = Transforms.computeFixedToIcrfMatrix(date, result);
if (!defined(fixedToIcrfMtx)) {
return undefined;
}
return Matrix3.transpose(fixedToIcrfMtx, result);
};
var xysScratch = new Iau2006XysSample(0.0, 0.0, 0.0);
var eopScratch = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
var rotation1Scratch = new Matrix3();
var rotation2Scratch = new Matrix3();
/**
* Computes a rotation matrix to transform a point or vector from the Earth-Fixed frame axes (ITRF)
* to the International Celestial Reference Frame (GCRF/ICRF) inertial frame axes
* at a given time. This function may return undefined if the data necessary to
* do the transformation is not yet loaded.
*
* @param {JulianDate} date The time at which to compute the rotation matrix.
* @param {Matrix3} [result] The object onto which to store the result. If this parameter is
* not specified, a new instance is created and returned.
* @returns {Matrix3} The rotation matrix, or undefined if the data necessary to do the
* transformation is not yet loaded.
*
*
* @example
* // Transform a point from the ICRF axes to the Fixed axes.
* var now = Cesium.JulianDate.now();
* var pointInFixed = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var fixedToIcrf = Cesium.Transforms.computeIcrfToFixedMatrix(now);
* var pointInInertial = new Cesium.Cartesian3();
* if (Cesium.defined(fixedToIcrf)) {
* pointInInertial = Cesium.Matrix3.multiplyByVector(fixedToIcrf, pointInFixed, pointInInertial);
* }
*
* @see Transforms.preloadIcrfFixed
*/
Transforms.computeFixedToIcrfMatrix = function(date, result) {
if (!defined(date)) {
throw new DeveloperError('date is required.');
}
if (!defined(result)) {
result = new Matrix3();
}
// Compute pole wander
var eop = Transforms.earthOrientationParameters.compute(date, eopScratch);
if (!defined(eop)) {
return undefined;
}
// There is no external conversion to Terrestrial Time (TT).
// So use International Atomic Time (TAI) and convert using offsets.
// Here we are assuming that dayTT and secondTT are positive
var dayTT = date.dayNumber;
// It's possible here that secondTT could roll over 86400
// This does not seem to affect the precision (unit tests check for this)
var secondTT = date.secondsOfDay + ttMinusTai;
var xys = Transforms.iau2006XysData.computeXysRadians(dayTT, secondTT, xysScratch);
if (!defined(xys)) {
return undefined;
}
var x = xys.x + eop.xPoleOffset;
var y = xys.y + eop.yPoleOffset;
// Compute XYS rotation
var a = 1.0 / (1.0 + Math.sqrt(1.0 - x * x - y * y));
var rotation1 = rotation1Scratch;
rotation1[0] = 1.0 - a * x * x;
rotation1[3] = -a * x * y;
rotation1[6] = x;
rotation1[1] = -a * x * y;
rotation1[4] = 1 - a * y * y;
rotation1[7] = y;
rotation1[2] = -x;
rotation1[5] = -y;
rotation1[8] = 1 - a * (x * x + y * y);
var rotation2 = Matrix3.fromRotationZ(-xys.s, rotation2Scratch);
var matrixQ = Matrix3.multiply(rotation1, rotation2, rotation1Scratch);
// Similar to TT conversions above
// It's possible here that secondTT could roll over 86400
// This does not seem to affect the precision (unit tests check for this)
var dateUt1day = date.dayNumber;
var dateUt1sec = date.secondsOfDay - JulianDate.computeTaiMinusUtc(date) + eop.ut1MinusUtc;
// Compute Earth rotation angle
// The IERS standard for era is
// era = 0.7790572732640 + 1.00273781191135448 * Tu
// where
// Tu = JulianDateInUt1 - 2451545.0
// However, you get much more precision if you make the following simplification
// era = a + (1 + b) * (JulianDayNumber + FractionOfDay - 2451545)
// era = a + (JulianDayNumber - 2451545) + FractionOfDay + b (JulianDayNumber - 2451545 + FractionOfDay)
// era = a + FractionOfDay + b (JulianDayNumber - 2451545 + FractionOfDay)
// since (JulianDayNumber - 2451545) represents an integer number of revolutions which will be discarded anyway.
var daysSinceJ2000 = dateUt1day - 2451545;
var fractionOfDay = dateUt1sec / TimeConstants.SECONDS_PER_DAY;
var era = 0.7790572732640 + fractionOfDay + 0.00273781191135448 * (daysSinceJ2000 + fractionOfDay);
era = (era % 1.0) * CesiumMath.TWO_PI;
var earthRotation = Matrix3.fromRotationZ(era, rotation2Scratch);
// pseudoFixed to ICRF
var pfToIcrf = Matrix3.multiply(matrixQ, earthRotation, rotation1Scratch);
// Compute pole wander matrix
var cosxp = Math.cos(eop.xPoleWander);
var cosyp = Math.cos(eop.yPoleWander);
var sinxp = Math.sin(eop.xPoleWander);
var sinyp = Math.sin(eop.yPoleWander);
var ttt = (dayTT - j2000ttDays) + secondTT / TimeConstants.SECONDS_PER_DAY;
ttt /= 36525.0;
// approximate sp value in rad
var sp = -47.0e-6 * ttt * CesiumMath.RADIANS_PER_DEGREE / 3600.0;
var cossp = Math.cos(sp);
var sinsp = Math.sin(sp);
var fToPfMtx = rotation2Scratch;
fToPfMtx[0] = cosxp * cossp;
fToPfMtx[1] = cosxp * sinsp;
fToPfMtx[2] = sinxp;
fToPfMtx[3] = -cosyp * sinsp + sinyp * sinxp * cossp;
fToPfMtx[4] = cosyp * cossp + sinyp * sinxp * sinsp;
fToPfMtx[5] = -sinyp * cosxp;
fToPfMtx[6] = -sinyp * sinsp - cosyp * sinxp * cossp;
fToPfMtx[7] = sinyp * cossp - cosyp * sinxp * sinsp;
fToPfMtx[8] = cosyp * cosxp;
return Matrix3.multiply(pfToIcrf, fToPfMtx, result);
};
var pointToWindowCoordinatesTemp = new Cartesian4();
/**
* Transform a point from model coordinates to window coordinates.
*
* @param {Matrix4} modelViewProjectionMatrix The 4x4 model-view-projection matrix.
* @param {Matrix4} viewportTransformation The 4x4 viewport transformation.
* @param {Cartesian3} point The point to transform.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided.
*/
Transforms.pointToWindowCoordinates = function (modelViewProjectionMatrix, viewportTransformation, point, result) {
result = Transforms.pointToGLWindowCoordinates(modelViewProjectionMatrix, viewportTransformation, point, result);
result.y = 2.0 * viewportTransformation[5] - result.y;
return result;
};
/**
* @private
*/
Transforms.pointToGLWindowCoordinates = function(modelViewProjectionMatrix, viewportTransformation, point, result) {
if (!defined(modelViewProjectionMatrix)) {
throw new DeveloperError('modelViewProjectionMatrix is required.');
}
if (!defined(viewportTransformation)) {
throw new DeveloperError('viewportTransformation is required.');
}
if (!defined(point)) {
throw new DeveloperError('point is required.');
}
if (!defined(result)) {
result = new Cartesian2();
}
var tmp = pointToWindowCoordinatesTemp;
Matrix4.multiplyByVector(modelViewProjectionMatrix, Cartesian4.fromElements(point.x, point.y, point.z, 1, tmp), tmp);
Cartesian4.multiplyByScalar(tmp, 1.0 / tmp.w, tmp);
Matrix4.multiplyByVector(viewportTransformation, tmp, tmp);
return Cartesian2.fromCartesian4(tmp, result);
};
var normalScratch = new Cartesian3();
var rightScratch = new Cartesian3();
var upScratch = new Cartesian3();
/**
* @private
*/
Transforms.rotationMatrixFromPositionVelocity = function(position, velocity, ellipsoid, result) {
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
if (!defined(velocity)) {
throw new DeveloperError('velocity is required.');
}
var normal = defaultValue(ellipsoid, Ellipsoid.WGS84).geodeticSurfaceNormal(position, normalScratch);
var right = Cartesian3.cross(velocity, normal, rightScratch);
if (Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
right = Cartesian3.clone(Cartesian3.UNIT_X, right);
}
var up = Cartesian3.cross(right, velocity, upScratch);
Cartesian3.cross(velocity, up, right);
Cartesian3.negate(right, right);
if (!defined(result)) {
result = new Matrix3();
}
result[0] = velocity.x;
result[1] = velocity.y;
result[2] = velocity.z;
result[3] = right.x;
result[4] = right.y;
result[5] = right.z;
result[6] = up.x;
result[7] = up.y;
result[8] = up.z;
return result;
};
var scratchCartographic = new Cartographic();
var scratchCartesian3Projection = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4Origin = new Cartesian4();
var scratchCartesian4NewOrigin = new Cartesian4();
var scratchCartesian4NewXAxis = new Cartesian4();
var scratchCartesian4NewYAxis = new Cartesian4();
var scratchCartesian4NewZAxis = new Cartesian4();
var scratchFromENU = new Matrix4();
var scratchToENU = new Matrix4();
/**
* @private
*/
Transforms.basisTo2D = function(projection, matrix, result) {
if (!defined(projection)) {
throw new DeveloperError('projection is required.');
}
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var ellipsoid = projection.ellipsoid;
var origin = Matrix4.getColumn(matrix, 3, scratchCartesian4Origin);
var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic);
var fromENU = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid, scratchFromENU);
var toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);
var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
var newOrigin = scratchCartesian4NewOrigin;
newOrigin.x = projectedPosition.z;
newOrigin.y = projectedPosition.x;
newOrigin.z = projectedPosition.y;
newOrigin.w = 1.0;
var xAxis = Matrix4.getColumn(matrix, 0, scratchCartesian3);
var xScale = Cartesian3.magnitude(xAxis);
var newXAxis = Matrix4.multiplyByVector(toENU, xAxis, scratchCartesian4NewXAxis);
Cartesian4.fromElements(newXAxis.z, newXAxis.x, newXAxis.y, 0.0, newXAxis);
var yAxis = Matrix4.getColumn(matrix, 1, scratchCartesian3);
var yScale = Cartesian3.magnitude(yAxis);
var newYAxis = Matrix4.multiplyByVector(toENU, yAxis, scratchCartesian4NewYAxis);
Cartesian4.fromElements(newYAxis.z, newYAxis.x, newYAxis.y, 0.0, newYAxis);
var zAxis = Matrix4.getColumn(matrix, 2, scratchCartesian3);
var zScale = Cartesian3.magnitude(zAxis);
var newZAxis = scratchCartesian4NewZAxis;
Cartesian3.cross(newXAxis, newYAxis, newZAxis);
Cartesian3.normalize(newZAxis, newZAxis);
Cartesian3.cross(newYAxis, newZAxis, newXAxis);
Cartesian3.normalize(newXAxis, newXAxis);
Cartesian3.cross(newZAxis, newXAxis, newYAxis);
Cartesian3.normalize(newYAxis, newYAxis);
Cartesian3.multiplyByScalar(newXAxis, xScale, newXAxis);
Cartesian3.multiplyByScalar(newYAxis, yScale, newYAxis);
Cartesian3.multiplyByScalar(newZAxis, zScale, newZAxis);
Matrix4.setColumn(result, 0, newXAxis, result);
Matrix4.setColumn(result, 1, newYAxis, result);
Matrix4.setColumn(result, 2, newZAxis, result);
Matrix4.setColumn(result, 3, newOrigin, result);
return result;
};
return Transforms;
});
/*global define*/
define('Core/EllipsoidTangentPlane',[
'./AxisAlignedBoundingBox',
'./Cartesian2',
'./Cartesian3',
'./Cartesian4',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./IntersectionTests',
'./Matrix4',
'./Plane',
'./Ray',
'./Transforms'
], function(
AxisAlignedBoundingBox,
Cartesian2,
Cartesian3,
Cartesian4,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
IntersectionTests,
Matrix4,
Plane,
Ray,
Transforms) {
'use strict';
var scratchCart4 = new Cartesian4();
/**
* A plane tangent to the provided ellipsoid at the provided origin.
* If origin is not on the surface of the ellipsoid, it's surface projection will be used.
* If origin is at the center of the ellipsoid, an exception will be thrown.
* @alias EllipsoidTangentPlane
* @constructor
*
* @param {Cartesian3} origin The point on the surface of the ellipsoid where the tangent plane touches.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use.
*
* @exception {DeveloperError} origin must not be at the center of the ellipsoid.
*/
function EllipsoidTangentPlane(origin, ellipsoid) {
if (!defined(origin)) {
throw new DeveloperError('origin is required.');
}
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
origin = ellipsoid.scaleToGeodeticSurface(origin);
if (!defined(origin)) {
throw new DeveloperError('origin must not be at the center of the ellipsoid.');
}
var eastNorthUp = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid);
this._ellipsoid = ellipsoid;
this._origin = origin;
this._xAxis = Cartesian3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 0, scratchCart4));
this._yAxis = Cartesian3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 1, scratchCart4));
var normal = Cartesian3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 2, scratchCart4));
this._plane = Plane.fromPointNormal(origin, normal);
}
defineProperties(EllipsoidTangentPlane.prototype, {
/**
* Gets the ellipsoid.
* @memberof EllipsoidTangentPlane.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Gets the origin.
* @memberof EllipsoidTangentPlane.prototype
* @type {Cartesian3}
*/
origin : {
get : function() {
return this._origin;
}
},
/**
* Gets the plane which is tangent to the ellipsoid.
* @memberof EllipsoidTangentPlane.prototype
* @readonly
* @type {Plane}
*/
plane : {
get : function() {
return this._plane;
}
},
/**
* Gets the local X-axis (east) of the tangent plane.
* @memberof EllipsoidTangentPlane.prototype
* @readonly
* @type {Cartesian3}
*/
xAxis : {
get : function() {
return this._xAxis;
}
},
/**
* Gets the local Y-axis (north) of the tangent plane.
* @memberof EllipsoidTangentPlane.prototype
* @readonly
* @type {Cartesian3}
*/
yAxis : {
get : function() {
return this._yAxis;
}
},
/**
* Gets the local Z-axis (up) of the tangent plane.
* @member EllipsoidTangentPlane.prototype
* @readonly
* @type {Cartesian3}
*/
zAxis : {
get : function() {
return this._plane.normal;
}
}
});
var tmp = new AxisAlignedBoundingBox();
/**
* Creates a new instance from the provided ellipsoid and the center
* point of the provided Cartesians.
*
* @param {Ellipsoid} ellipsoid The ellipsoid to use.
* @param {Cartesian3} cartesians The list of positions surrounding the center point.
*/
EllipsoidTangentPlane.fromPoints = function(cartesians, ellipsoid) {
if (!defined(cartesians)) {
throw new DeveloperError('cartesians is required.');
}
var box = AxisAlignedBoundingBox.fromPoints(cartesians, tmp);
return new EllipsoidTangentPlane(box.center, ellipsoid);
};
var scratchProjectPointOntoPlaneRay = new Ray();
var scratchProjectPointOntoPlaneCartesian3 = new Cartesian3();
/**
* Computes the projection of the provided 3D position onto the 2D plane, radially outward from the {@link EllipsoidTangentPlane.ellipsoid} coordinate system origin.
*
* @param {Cartesian3} cartesian The point to project.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided. Undefined if there is no intersection point
*/
EllipsoidTangentPlane.prototype.projectPointOntoPlane = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
var ray = scratchProjectPointOntoPlaneRay;
ray.origin = cartesian;
Cartesian3.normalize(cartesian, ray.direction);
var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
if (!defined(intersectionPoint)) {
Cartesian3.negate(ray.direction, ray.direction);
intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
}
if (defined(intersectionPoint)) {
var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint);
var x = Cartesian3.dot(this._xAxis, v);
var y = Cartesian3.dot(this._yAxis, v);
if (!defined(result)) {
return new Cartesian2(x, y);
}
result.x = x;
result.y = y;
return result;
}
return undefined;
};
/**
* Computes the projection of the provided 3D positions onto the 2D plane (where possible), radially outward from the global origin.
* The resulting array may be shorter than the input array - if a single projection is impossible it will not be included.
*
* @see EllipsoidTangentPlane.projectPointOntoPlane
*
* @param {Cartesian3[]} cartesians The array of points to project.
* @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results.
* @returns {Cartesian2[]} The modified result parameter or a new array of Cartesian2 instances if none was provided.
*/
EllipsoidTangentPlane.prototype.projectPointsOntoPlane = function(cartesians, result) {
if (!defined(cartesians)) {
throw new DeveloperError('cartesians is required.');
}
if (!defined(result)) {
result = [];
}
var count = 0;
var length = cartesians.length;
for ( var i = 0; i < length; i++) {
var p = this.projectPointOntoPlane(cartesians[i], result[count]);
if (defined(p)) {
result[count] = p;
count++;
}
}
result.length = count;
return result;
};
/**
* Computes the projection of the provided 3D position onto the 2D plane, along the plane normal.
*
* @param {Cartesian3} cartesian The point to project.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided.
*/
EllipsoidTangentPlane.prototype.projectPointToNearestOnPlane = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
if (!defined(result)) {
result = new Cartesian2();
}
var ray = scratchProjectPointOntoPlaneRay;
ray.origin = cartesian;
Cartesian3.clone(this._plane.normal, ray.direction);
var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
if (!defined(intersectionPoint)) {
Cartesian3.negate(ray.direction, ray.direction);
intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
}
var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint);
var x = Cartesian3.dot(this._xAxis, v);
var y = Cartesian3.dot(this._yAxis, v);
result.x = x;
result.y = y;
return result;
};
/**
* Computes the projection of the provided 3D positions onto the 2D plane, along the plane normal.
*
* @see EllipsoidTangentPlane.projectPointToNearestOnPlane
*
* @param {Cartesian3[]} cartesians The array of points to project.
* @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results.
* @returns {Cartesian2[]} The modified result parameter or a new array of Cartesian2 instances if none was provided. This will have the same length as cartesians
.
*/
EllipsoidTangentPlane.prototype.projectPointsToNearestOnPlane = function(cartesians, result) {
if (!defined(cartesians)) {
throw new DeveloperError('cartesians is required.');
}
if (!defined(result)) {
result = [];
}
var length = cartesians.length;
result.length = length;
for (var i = 0; i < length; i++) {
result[i] = this.projectPointToNearestOnPlane(cartesians[i], result[i]);
}
return result;
};
var projectPointsOntoEllipsoidScratch = new Cartesian3();
/**
* Computes the projection of the provided 2D positions onto the 3D ellipsoid.
*
* @param {Cartesian2[]} cartesians The array of points to project.
* @param {Cartesian3[]} [result] The array of Cartesian3 instances onto which to store results.
* @returns {Cartesian3[]} The modified result parameter or a new array of Cartesian3 instances if none was provided.
*/
EllipsoidTangentPlane.prototype.projectPointsOntoEllipsoid = function(cartesians, result) {
if (!defined(cartesians)) {
throw new DeveloperError('cartesians is required.');
}
var length = cartesians.length;
if (!defined(result)) {
result = new Array(length);
} else {
result.length = length;
}
var ellipsoid = this._ellipsoid;
var origin = this._origin;
var xAxis = this._xAxis;
var yAxis = this._yAxis;
var tmp = projectPointsOntoEllipsoidScratch;
for ( var i = 0; i < length; ++i) {
var position = cartesians[i];
Cartesian3.multiplyByScalar(xAxis, position.x, tmp);
if (!defined(result[i])) {
result[i] = new Cartesian3();
}
var point = Cartesian3.add(origin, tmp, result[i]);
Cartesian3.multiplyByScalar(yAxis, position.y, tmp);
Cartesian3.add(point, tmp, point);
ellipsoid.scaleToGeocentricSurface(point, point);
}
return result;
};
return EllipsoidTangentPlane;
});
/*global define*/
define('Core/OrientedBoundingBox',[
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./EllipsoidTangentPlane',
'./Intersect',
'./Interval',
'./Math',
'./Matrix3',
'./Plane',
'./Rectangle'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartographic,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
EllipsoidTangentPlane,
Intersect,
Interval,
CesiumMath,
Matrix3,
Plane,
Rectangle) {
'use strict';
/**
* Creates an instance of an OrientedBoundingBox.
* An OrientedBoundingBox of some object is a closed and convex cuboid. It can provide a tighter bounding volume than {@link BoundingSphere} or {@link AxisAlignedBoundingBox} in many cases.
* @alias OrientedBoundingBox
* @constructor
*
* @param {Cartesian3} [center=Cartesian3.ZERO] The center of the box.
* @param {Matrix3} [halfAxes=Matrix3.ZERO] The three orthogonal half-axes of the bounding box.
* Equivalently, the transformation matrix, to rotate and scale a 2x2x2
* cube centered at the origin.
*
*
* @example
* // Create an OrientedBoundingBox using a transformation matrix, a position where the box will be translated, and a scale.
* var center = new Cesium.Cartesian3(1.0, 0.0, 0.0);
* var halfAxes = Cesium.Matrix3.fromScale(new Cesium.Cartesian3(1.0, 3.0, 2.0), new Cesium.Matrix3());
*
* var obb = new Cesium.OrientedBoundingBox(center, halfAxes);
*
* @see BoundingSphere
* @see BoundingRectangle
*/
function OrientedBoundingBox(center, halfAxes) {
/**
* The center of the box.
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO));
/**
* The transformation matrix, to rotate the box to the right position.
* @type {Matrix3}
* @default {@link Matrix3.IDENTITY}
*/
this.halfAxes = Matrix3.clone(defaultValue(halfAxes, Matrix3.ZERO));
}
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4 = new Cartesian3();
var scratchCartesian5 = new Cartesian3();
var scratchCartesian6 = new Cartesian3();
var scratchCovarianceResult = new Matrix3();
var scratchEigenResult = {
unitary : new Matrix3(),
diagonal : new Matrix3()
};
/**
* Computes an instance of an OrientedBoundingBox of the given positions.
* This is an implementation of Stefan Gottschalk's Collision Queries using Oriented Bounding Boxes solution (PHD thesis).
* Reference: http://gamma.cs.unc.edu/users/gottschalk/main.pdf
*
* @param {Cartesian3[]} positions List of {@link Cartesian3} points that the bounding box will enclose.
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
*
* @example
* // Compute an object oriented bounding box enclosing two points.
* var box = Cesium.OrientedBoundingBox.fromPoints([new Cesium.Cartesian3(2, 0, 0), new Cesium.Cartesian3(-2, 0, 0)]);
*/
OrientedBoundingBox.fromPoints = function(positions, result) {
if (!defined(result)) {
result = new OrientedBoundingBox();
}
if (!defined(positions) || positions.length === 0) {
result.halfAxes = Matrix3.ZERO;
result.center = Cartesian3.ZERO;
return result;
}
var i;
var length = positions.length;
var meanPoint = Cartesian3.clone(positions[0], scratchCartesian1);
for (i = 1; i < length; i++) {
Cartesian3.add(meanPoint, positions[i], meanPoint);
}
var invLength = 1.0 / length;
Cartesian3.multiplyByScalar(meanPoint, invLength, meanPoint);
var exx = 0.0;
var exy = 0.0;
var exz = 0.0;
var eyy = 0.0;
var eyz = 0.0;
var ezz = 0.0;
var p;
for (i = 0; i < length; i++) {
p = Cartesian3.subtract(positions[i], meanPoint, scratchCartesian2);
exx += p.x * p.x;
exy += p.x * p.y;
exz += p.x * p.z;
eyy += p.y * p.y;
eyz += p.y * p.z;
ezz += p.z * p.z;
}
exx *= invLength;
exy *= invLength;
exz *= invLength;
eyy *= invLength;
eyz *= invLength;
ezz *= invLength;
var covarianceMatrix = scratchCovarianceResult;
covarianceMatrix[0] = exx;
covarianceMatrix[1] = exy;
covarianceMatrix[2] = exz;
covarianceMatrix[3] = exy;
covarianceMatrix[4] = eyy;
covarianceMatrix[5] = eyz;
covarianceMatrix[6] = exz;
covarianceMatrix[7] = eyz;
covarianceMatrix[8] = ezz;
var eigenDecomposition = Matrix3.computeEigenDecomposition(covarianceMatrix, scratchEigenResult);
var rotation = Matrix3.clone(eigenDecomposition.unitary, result.halfAxes);
var v1 = Matrix3.getColumn(rotation, 0, scratchCartesian4);
var v2 = Matrix3.getColumn(rotation, 1, scratchCartesian5);
var v3 = Matrix3.getColumn(rotation, 2, scratchCartesian6);
var u1 = -Number.MAX_VALUE;
var u2 = -Number.MAX_VALUE;
var u3 = -Number.MAX_VALUE;
var l1 = Number.MAX_VALUE;
var l2 = Number.MAX_VALUE;
var l3 = Number.MAX_VALUE;
for (i = 0; i < length; i++) {
p = positions[i];
u1 = Math.max(Cartesian3.dot(v1, p), u1);
u2 = Math.max(Cartesian3.dot(v2, p), u2);
u3 = Math.max(Cartesian3.dot(v3, p), u3);
l1 = Math.min(Cartesian3.dot(v1, p), l1);
l2 = Math.min(Cartesian3.dot(v2, p), l2);
l3 = Math.min(Cartesian3.dot(v3, p), l3);
}
v1 = Cartesian3.multiplyByScalar(v1, 0.5 * (l1 + u1), v1);
v2 = Cartesian3.multiplyByScalar(v2, 0.5 * (l2 + u2), v2);
v3 = Cartesian3.multiplyByScalar(v3, 0.5 * (l3 + u3), v3);
var center = Cartesian3.add(v1, v2, result.center);
center = Cartesian3.add(center, v3, center);
var scale = scratchCartesian3;
scale.x = u1 - l1;
scale.y = u2 - l2;
scale.z = u3 - l3;
Cartesian3.multiplyByScalar(scale, 0.5, scale);
Matrix3.multiplyByScale(result.halfAxes, scale, result.halfAxes);
return result;
};
var scratchOffset = new Cartesian3();
var scratchScale = new Cartesian3();
/**
* Computes an OrientedBoundingBox given extents in the east-north-up space of the tangent plane.
*
* @param {Number} minimumX Minimum X extent in tangent plane space.
* @param {Number} maximumX Maximum X extent in tangent plane space.
* @param {Number} minimumY Minimum Y extent in tangent plane space.
* @param {Number} maximumY Maximum Y extent in tangent plane space.
* @param {Number} minimumZ Minimum Z extent in tangent plane space.
* @param {Number} maximumZ Maximum Z extent in tangent plane space.
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
*/
function fromTangentPlaneExtents(tangentPlane, minimumX, maximumX, minimumY, maximumY, minimumZ, maximumZ, result) {
if (!defined(minimumX) ||
!defined(maximumX) ||
!defined(minimumY) ||
!defined(maximumY) ||
!defined(minimumZ) ||
!defined(maximumZ)) {
throw new DeveloperError('all extents (minimum/maximum X/Y/Z) are required.');
}
if (!defined(result)) {
result = new OrientedBoundingBox();
}
var halfAxes = result.halfAxes;
Matrix3.setColumn(halfAxes, 0, tangentPlane.xAxis, halfAxes);
Matrix3.setColumn(halfAxes, 1, tangentPlane.yAxis, halfAxes);
Matrix3.setColumn(halfAxes, 2, tangentPlane.zAxis, halfAxes);
var centerOffset = scratchOffset;
centerOffset.x = (minimumX + maximumX) / 2.0;
centerOffset.y = (minimumY + maximumY) / 2.0;
centerOffset.z = (minimumZ + maximumZ) / 2.0;
var scale = scratchScale;
scale.x = (maximumX - minimumX) / 2.0;
scale.y = (maximumY - minimumY) / 2.0;
scale.z = (maximumZ - minimumZ) / 2.0;
var center = result.center;
centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset);
Cartesian3.add(tangentPlane.origin, centerOffset, center);
Matrix3.multiplyByScale(halfAxes, scale, halfAxes);
return result;
}
var scratchRectangleCenterCartographic = new Cartographic();
var scratchRectangleCenter = new Cartesian3();
var perimeterCartographicScratch = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
var perimeterCartesianScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
var perimeterProjectedScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()];
/**
* Computes an OrientedBoundingBox that bounds a {@link Rectangle} on the surface of an {@link Ellipsoid}.
* There are no guarantees about the orientation of the bounding box.
*
* @param {Rectangle} rectangle The cartographic rectangle on the surface of the ellipsoid.
* @param {Number} [minimumHeight=0.0] The minimum height (elevation) within the tile.
* @param {Number} [maximumHeight=0.0] The maximum height (elevation) within the tile.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle is defined.
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided.
*
* @exception {DeveloperError} rectangle.width must be between 0 and pi.
* @exception {DeveloperError} rectangle.height must be between 0 and pi.
* @exception {DeveloperError} ellipsoid must be an ellipsoid of revolution (radii.x == radii.y
)
*/
OrientedBoundingBox.fromRectangle = function(rectangle, minimumHeight, maximumHeight, ellipsoid, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required');
}
if (rectangle.width < 0.0 || rectangle.width > CesiumMath.PI) {
throw new DeveloperError('Rectangle width must be between 0 and pi');
}
if (rectangle.height < 0.0 || rectangle.height > CesiumMath.PI) {
throw new DeveloperError('Rectangle height must be between 0 and pi');
}
if (defined(ellipsoid) && !CesiumMath.equalsEpsilon(ellipsoid.radii.x, ellipsoid.radii.y, CesiumMath.EPSILON15)) {
throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)');
}
minimumHeight = defaultValue(minimumHeight, 0.0);
maximumHeight = defaultValue(maximumHeight, 0.0);
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
// The bounding box will be aligned with the tangent plane at the center of the rectangle.
var tangentPointCartographic = Rectangle.center(rectangle, scratchRectangleCenterCartographic);
var tangentPoint = ellipsoid.cartographicToCartesian(tangentPointCartographic, scratchRectangleCenter);
var tangentPlane = new EllipsoidTangentPlane(tangentPoint, ellipsoid);
var plane = tangentPlane.plane;
// Corner arrangement:
// N/+y
// [0] [1] [2]
// W/-x [7] [3] E/+x
// [6] [5] [4]
// S/-y
// "C" refers to the central lat/long, which by default aligns with the tangent point (above).
// If the rectangle spans the equator, CW and CE are instead aligned with the equator.
var perimeterNW = perimeterCartographicScratch[0];
var perimeterNC = perimeterCartographicScratch[1];
var perimeterNE = perimeterCartographicScratch[2];
var perimeterCE = perimeterCartographicScratch[3];
var perimeterSE = perimeterCartographicScratch[4];
var perimeterSC = perimeterCartographicScratch[5];
var perimeterSW = perimeterCartographicScratch[6];
var perimeterCW = perimeterCartographicScratch[7];
var lonCenter = tangentPointCartographic.longitude;
var latCenter = (rectangle.south < 0.0 && rectangle.north > 0.0) ? 0.0 : tangentPointCartographic.latitude;
perimeterSW.latitude = perimeterSC.latitude = perimeterSE.latitude = rectangle.south;
perimeterCW.latitude = perimeterCE.latitude = latCenter;
perimeterNW.latitude = perimeterNC.latitude = perimeterNE.latitude = rectangle.north;
perimeterSW.longitude = perimeterCW.longitude = perimeterNW.longitude = rectangle.west;
perimeterSC.longitude = perimeterNC.longitude = lonCenter;
perimeterSE.longitude = perimeterCE.longitude = perimeterNE.longitude = rectangle.east;
// Compute XY extents using the rectangle at maximum height
perimeterNE.height = perimeterNC.height = perimeterNW.height = perimeterCW.height = perimeterSW.height = perimeterSC.height = perimeterSE.height = perimeterCE.height = maximumHeight;
ellipsoid.cartographicArrayToCartesianArray(perimeterCartographicScratch, perimeterCartesianScratch);
tangentPlane.projectPointsToNearestOnPlane(perimeterCartesianScratch, perimeterProjectedScratch);
// See the `perimeterXX` definitions above for what these are
var minX = Math.min(perimeterProjectedScratch[6].x, perimeterProjectedScratch[7].x, perimeterProjectedScratch[0].x);
var maxX = Math.max(perimeterProjectedScratch[2].x, perimeterProjectedScratch[3].x, perimeterProjectedScratch[4].x);
var minY = Math.min(perimeterProjectedScratch[4].y, perimeterProjectedScratch[5].y, perimeterProjectedScratch[6].y);
var maxY = Math.max(perimeterProjectedScratch[0].y, perimeterProjectedScratch[1].y, perimeterProjectedScratch[2].y);
// Compute minimum Z using the rectangle at minimum height
perimeterNE.height = perimeterNW.height = perimeterSE.height = perimeterSW.height = minimumHeight;
ellipsoid.cartographicArrayToCartesianArray(perimeterCartographicScratch, perimeterCartesianScratch);
var minZ = Math.min(Plane.getPointDistance(plane, perimeterCartesianScratch[0]),
Plane.getPointDistance(plane, perimeterCartesianScratch[2]),
Plane.getPointDistance(plane, perimeterCartesianScratch[4]),
Plane.getPointDistance(plane, perimeterCartesianScratch[6]));
var maxZ = maximumHeight; // Since the tangent plane touches the surface at height = 0, this is okay
return fromTangentPlaneExtents(tangentPlane, minX, maxX, minY, maxY, minZ, maxZ, result);
};
/**
* Duplicates a OrientedBoundingBox instance.
*
* @param {OrientedBoundingBox} box The bounding box to duplicate.
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided. (Returns undefined if box is undefined)
*/
OrientedBoundingBox.clone = function(box, result) {
if (!defined(box)) {
return undefined;
}
if (!defined(result)) {
return new OrientedBoundingBox(box.center, box.halfAxes);
}
Cartesian3.clone(box.center, result.center);
Matrix3.clone(box.halfAxes, result.halfAxes);
return result;
};
/**
* Determines which side of a plane the oriented bounding box is located.
*
* @param {OrientedBoundingBox} box The oriented bounding box to test.
* @param {Plane} plane The plane to test against.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
* intersects the plane.
*/
OrientedBoundingBox.intersectPlane = function(box, plane) {
if (!defined(box)) {
throw new DeveloperError('box is required.');
}
if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
var center = box.center;
var normal = plane.normal;
var halfAxes = box.halfAxes;
var normalX = normal.x, normalY = normal.y, normalZ = normal.z;
// plane is used as if it is its normal; the first three components are assumed to be normalized
var radEffective = Math.abs(normalX * halfAxes[Matrix3.COLUMN0ROW0] + normalY * halfAxes[Matrix3.COLUMN0ROW1] + normalZ * halfAxes[Matrix3.COLUMN0ROW2]) +
Math.abs(normalX * halfAxes[Matrix3.COLUMN1ROW0] + normalY * halfAxes[Matrix3.COLUMN1ROW1] + normalZ * halfAxes[Matrix3.COLUMN1ROW2]) +
Math.abs(normalX * halfAxes[Matrix3.COLUMN2ROW0] + normalY * halfAxes[Matrix3.COLUMN2ROW1] + normalZ * halfAxes[Matrix3.COLUMN2ROW2]);
var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance;
if (distanceToPlane <= -radEffective) {
// The entire box is on the negative side of the plane normal
return Intersect.OUTSIDE;
} else if (distanceToPlane >= radEffective) {
// The entire box is on the positive side of the plane normal
return Intersect.INSIDE;
}
return Intersect.INTERSECTING;
};
var scratchCartesianU = new Cartesian3();
var scratchCartesianV = new Cartesian3();
var scratchCartesianW = new Cartesian3();
var scratchPPrime = new Cartesian3();
/**
* Computes the estimated distance squared from the closest point on a bounding box to a point.
*
* @param {OrientedBoundingBox} box The box.
* @param {Cartesian3} cartesian The point
* @returns {Number} The estimated distance squared from the bounding sphere to the point.
*
* @example
* // Sort bounding boxes from back to front
* boxes.sort(function(a, b) {
* return Cesium.OrientedBoundingBox.distanceSquaredTo(b, camera.positionWC) - Cesium.OrientedBoundingBox.distanceSquaredTo(a, camera.positionWC);
* });
*/
OrientedBoundingBox.distanceSquaredTo = function(box, cartesian) {
// See Geometric Tools for Computer Graphics 10.4.2
if (!defined(box)) {
throw new DeveloperError('box is required.');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required.');
}
var offset = Cartesian3.subtract(cartesian, box.center, scratchOffset);
var halfAxes = box.halfAxes;
var u = Matrix3.getColumn(halfAxes, 0, scratchCartesianU);
var v = Matrix3.getColumn(halfAxes, 1, scratchCartesianV);
var w = Matrix3.getColumn(halfAxes, 2, scratchCartesianW);
var uHalf = Cartesian3.magnitude(u);
var vHalf = Cartesian3.magnitude(v);
var wHalf = Cartesian3.magnitude(w);
Cartesian3.normalize(u, u);
Cartesian3.normalize(v, v);
Cartesian3.normalize(w, w);
var pPrime = scratchPPrime;
pPrime.x = Cartesian3.dot(offset, u);
pPrime.y = Cartesian3.dot(offset, v);
pPrime.z = Cartesian3.dot(offset, w);
var distanceSquared = 0.0;
var d;
if (pPrime.x < -uHalf) {
d = pPrime.x + uHalf;
distanceSquared += d * d;
} else if (pPrime.x > uHalf) {
d = pPrime.x - uHalf;
distanceSquared += d * d;
}
if (pPrime.y < -vHalf) {
d = pPrime.y + vHalf;
distanceSquared += d * d;
} else if (pPrime.y > vHalf) {
d = pPrime.y - vHalf;
distanceSquared += d * d;
}
if (pPrime.z < -wHalf) {
d = pPrime.z + wHalf;
distanceSquared += d * d;
} else if (pPrime.z > wHalf) {
d = pPrime.z - wHalf;
distanceSquared += d * d;
}
return distanceSquared;
};
var scratchCorner = new Cartesian3();
var scratchToCenter = new Cartesian3();
/**
* The distances calculated by the vector from the center of the bounding box to position projected onto direction.
*
* If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
* closest and farthest planes from position that intersect the bounding box.
*
* @param {OrientedBoundingBox} box The bounding box to calculate the distance to.
* @param {Cartesian3} position The position to calculate the distance from.
* @param {Cartesian3} direction The direction from position.
* @param {Interval} [result] A Interval to store the nearest and farthest distances.
* @returns {Interval} The nearest and farthest distances on the bounding box from position in direction.
*/
OrientedBoundingBox.computePlaneDistances = function(box, position, direction, result) {
if (!defined(box)) {
throw new DeveloperError('box is required.');
}
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
if (!defined(direction)) {
throw new DeveloperError('direction is required.');
}
if (!defined(result)) {
result = new Interval();
}
var minDist = Number.POSITIVE_INFINITY;
var maxDist = Number.NEGATIVE_INFINITY;
var center = box.center;
var halfAxes = box.halfAxes;
var u = Matrix3.getColumn(halfAxes, 0, scratchCartesianU);
var v = Matrix3.getColumn(halfAxes, 1, scratchCartesianV);
var w = Matrix3.getColumn(halfAxes, 2, scratchCartesianW);
// project first corner
var corner = Cartesian3.add(u, v, scratchCorner);
Cartesian3.add(corner, w, corner);
Cartesian3.add(corner, center, corner);
var toCenter = Cartesian3.subtract(corner, position, scratchToCenter);
var mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project second corner
Cartesian3.add(center, u, corner);
Cartesian3.add(corner, v, corner);
Cartesian3.subtract(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project third corner
Cartesian3.add(center, u, corner);
Cartesian3.subtract(corner, v, corner);
Cartesian3.add(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project fourth corner
Cartesian3.add(center, u, corner);
Cartesian3.subtract(corner, v, corner);
Cartesian3.subtract(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project fifth corner
Cartesian3.subtract(center, u, corner);
Cartesian3.add(corner, v, corner);
Cartesian3.add(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project sixth corner
Cartesian3.subtract(center, u, corner);
Cartesian3.add(corner, v, corner);
Cartesian3.subtract(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project seventh corner
Cartesian3.subtract(center, u, corner);
Cartesian3.subtract(corner, v, corner);
Cartesian3.add(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
// project eighth corner
Cartesian3.subtract(center, u, corner);
Cartesian3.subtract(corner, v, corner);
Cartesian3.subtract(corner, w, corner);
Cartesian3.subtract(corner, position, toCenter);
mag = Cartesian3.dot(direction, toCenter);
minDist = Math.min(mag, minDist);
maxDist = Math.max(mag, maxDist);
result.start = minDist;
result.stop = maxDist;
return result;
};
var scratchBoundingSphere = new BoundingSphere();
/**
* Determines whether or not a bounding box is hidden from view by the occluder.
*
* @param {OrientedBoundingBox} box The bounding box surrounding the occludee object.
* @param {Occluder} occluder The occluder.
* @returns {Boolean} true
if the box is not visible; otherwise false
.
*/
OrientedBoundingBox.isOccluded = function(box, occluder) {
if (!defined(box)) {
throw new DeveloperError('box is required.');
}
if (!defined(occluder)) {
throw new DeveloperError('occluder is required.');
}
var sphere = BoundingSphere.fromOrientedBoundingBox(box, scratchBoundingSphere);
return !occluder.isBoundingSphereVisible(sphere);
};
/**
* Determines which side of a plane the oriented bounding box is located.
*
* @param {Plane} plane The plane to test against.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
* intersects the plane.
*/
OrientedBoundingBox.prototype.intersectPlane = function(plane) {
return OrientedBoundingBox.intersectPlane(this, plane);
};
/**
* Computes the estimated distance squared from the closest point on a bounding box to a point.
*
* @param {Cartesian3} cartesian The point
* @returns {Number} The estimated distance squared from the bounding sphere to the point.
*
* @example
* // Sort bounding boxes from back to front
* boxes.sort(function(a, b) {
* return b.distanceSquaredTo(camera.positionWC) - a.distanceSquaredTo(camera.positionWC);
* });
*/
OrientedBoundingBox.prototype.distanceSquaredTo = function(cartesian) {
return OrientedBoundingBox.distanceSquaredTo(this, cartesian);
};
/**
* The distances calculated by the vector from the center of the bounding box to position projected onto direction.
*
* If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
* closest and farthest planes from position that intersect the bounding box.
*
* @param {Cartesian3} position The position to calculate the distance from.
* @param {Cartesian3} direction The direction from position.
* @param {Interval} [result] A Interval to store the nearest and farthest distances.
* @returns {Interval} The nearest and farthest distances on the bounding box from position in direction.
*/
OrientedBoundingBox.prototype.computePlaneDistances = function(position, direction, result) {
return OrientedBoundingBox.computePlaneDistances(this, position, direction, result);
};
/**
* Determines whether or not a bounding box is hidden from view by the occluder.
*
* @param {Occluder} occluder The occluder.
* @returns {Boolean} true
if the sphere is not visible; otherwise false
.
*/
OrientedBoundingBox.prototype.isOccluded = function(occluder) {
return OrientedBoundingBox.isOccluded(this, occluder);
};
/**
* Compares the provided OrientedBoundingBox componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {OrientedBoundingBox} left The first OrientedBoundingBox.
* @param {OrientedBoundingBox} right The second OrientedBoundingBox.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
OrientedBoundingBox.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
Cartesian3.equals(left.center, right.center) &&
Matrix3.equals(left.halfAxes, right.halfAxes));
};
/**
* Duplicates this OrientedBoundingBox instance.
*
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
*/
OrientedBoundingBox.prototype.clone = function(result) {
return OrientedBoundingBox.clone(this, result);
};
/**
* Compares this OrientedBoundingBox against the provided OrientedBoundingBox componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {OrientedBoundingBox} [right] The right hand side OrientedBoundingBox.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
OrientedBoundingBox.prototype.equals = function(right) {
return OrientedBoundingBox.equals(this, right);
};
return OrientedBoundingBox;
});
/*global define*/
define('Core/AttributeCompression',[
'./Cartesian2',
'./Cartesian3',
'./defined',
'./DeveloperError',
'./Math'
], function(
Cartesian2,
Cartesian3,
defined,
DeveloperError,
CesiumMath) {
'use strict';
/**
* Attribute compression and decompression functions.
*
* @exports AttributeCompression
*
* @private
*/
var AttributeCompression = {};
/**
* Encodes a normalized vector into 2 SNORM values in the range of [0-rangeMax] following the 'oct' encoding.
*
* Oct encoding is a compact representation of unit length vectors.
* The 'oct' encoding is described in "A Survey of Efficient Representations of Independent Unit Vectors",
* Cigolle et al 2014: {@link http://jcgt.org/published/0003/02/01/}
*
* @param {Cartesian3} vector The normalized vector to be compressed into 2 component 'oct' encoding.
* @param {Cartesian2} result The 2 component oct-encoded unit length vector.
* @param {Number} rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.
* @returns {Cartesian2} The 2 component oct-encoded unit length vector.
*
* @exception {DeveloperError} vector must be normalized.
*
* @see AttributeCompression.octDecodeInRange
*/
AttributeCompression.octEncodeInRange = function(vector, rangeMax, result) {
if (!defined(vector)) {
throw new DeveloperError('vector is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var magSquared = Cartesian3.magnitudeSquared(vector);
if (Math.abs(magSquared - 1.0) > CesiumMath.EPSILON6) {
throw new DeveloperError('vector must be normalized.');
}
result.x = vector.x / (Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z));
result.y = vector.y / (Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z));
if (vector.z < 0) {
var x = result.x;
var y = result.y;
result.x = (1.0 - Math.abs(y)) * CesiumMath.signNotZero(x);
result.y = (1.0 - Math.abs(x)) * CesiumMath.signNotZero(y);
}
result.x = CesiumMath.toSNorm(result.x, rangeMax);
result.y = CesiumMath.toSNorm(result.y, rangeMax);
return result;
};
/**
* Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding.
*
* @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding.
* @param {Cartesian2} result The 2 byte oct-encoded unit length vector.
* @returns {Cartesian2} The 2 byte oct-encoded unit length vector.
*
* @exception {DeveloperError} vector must be normalized.
*
* @see AttributeCompression.octEncodeInRange
* @see AttributeCompression.octDecode
*/
AttributeCompression.octEncode = function(vector, result) {
return AttributeCompression.octEncodeInRange(vector, 255, result);
};
/**
* Decodes a unit-length vector in 'oct' encoding to a normalized 3-component vector.
*
* @param {Number} x The x component of the oct-encoded unit length vector.
* @param {Number} y The y component of the oct-encoded unit length vector.
* @param {Number} rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.
* @param {Cartesian3} result The decoded and normalized vector
* @returns {Cartesian3} The decoded and normalized vector.
*
* @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and rangeMax.
*
* @see AttributeCompression.octEncodeInRange
*/
AttributeCompression.octDecodeInRange = function(x, y, rangeMax, result) {
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
if (x < 0 || x > rangeMax || y < 0 || y > rangeMax) {
throw new DeveloperError('x and y must be a signed normalized integer between 0 and ' + rangeMax);
}
result.x = CesiumMath.fromSNorm(x, rangeMax);
result.y = CesiumMath.fromSNorm(y, rangeMax);
result.z = 1.0 - (Math.abs(result.x) + Math.abs(result.y));
if (result.z < 0.0)
{
var oldVX = result.x;
result.x = (1.0 - Math.abs(result.y)) * CesiumMath.signNotZero(oldVX);
result.y = (1.0 - Math.abs(oldVX)) * CesiumMath.signNotZero(result.y);
}
return Cartesian3.normalize(result, result);
};
/**
* Decodes a unit-length vector in 2 byte 'oct' encoding to a normalized 3-component vector.
*
* @param {Number} x The x component of the oct-encoded unit length vector.
* @param {Number} y The y component of the oct-encoded unit length vector.
* @param {Cartesian3} result The decoded and normalized vector.
* @returns {Cartesian3} The decoded and normalized vector.
*
* @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and 255.
*
* @see AttributeCompression.octDecodeInRange
*/
AttributeCompression.octDecode = function(x, y, result) {
return AttributeCompression.octDecodeInRange(x, y, 255, result);
};
/**
* Packs an oct encoded vector into a single floating-point number.
*
* @param {Cartesian2} encoded The oct encoded vector.
* @returns {Number} The oct encoded vector packed into a single float.
*
*/
AttributeCompression.octPackFloat = function(encoded) {
if (!defined(encoded)) {
throw new DeveloperError('encoded is required.');
}
return 256.0 * encoded.x + encoded.y;
};
var scratchEncodeCart2 = new Cartesian2();
/**
* Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding and
* stores those values in a single float-point number.
*
* @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding.
* @returns {Number} The 2 byte oct-encoded unit length vector.
*
* @exception {DeveloperError} vector must be normalized.
*/
AttributeCompression.octEncodeFloat = function(vector) {
AttributeCompression.octEncode(vector, scratchEncodeCart2);
return AttributeCompression.octPackFloat(scratchEncodeCart2);
};
/**
* Decodes a unit-length vector in 'oct' encoding packed in a floating-point number to a normalized 3-component vector.
*
* @param {Number} value The oct-encoded unit length vector stored as a single floating-point number.
* @param {Cartesian3} result The decoded and normalized vector
* @returns {Cartesian3} The decoded and normalized vector.
*
*/
AttributeCompression.octDecodeFloat = function(value, result) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var temp = value / 256.0;
var x = Math.floor(temp);
var y = (temp - x) * 256.0;
return AttributeCompression.octDecode(x, y, result);
};
/**
* Encodes three normalized vectors into 6 SNORM values in the range of [0-255] following the 'oct' encoding and
* packs those into two floating-point numbers.
*
* @param {Cartesian3} v1 A normalized vector to be compressed.
* @param {Cartesian3} v2 A normalized vector to be compressed.
* @param {Cartesian3} v3 A normalized vector to be compressed.
* @param {Cartesian2} result The 'oct' encoded vectors packed into two floating-point numbers.
* @returns {Cartesian2} The 'oct' encoded vectors packed into two floating-point numbers.
*
*/
AttributeCompression.octPack = function(v1, v2, v3, result) {
if (!defined(v1)) {
throw new DeveloperError('v1 is required.');
}
if (!defined(v2)) {
throw new DeveloperError('v2 is required.');
}
if (!defined(v3)) {
throw new DeveloperError('v3 is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var encoded1 = AttributeCompression.octEncodeFloat(v1);
var encoded2 = AttributeCompression.octEncodeFloat(v2);
var encoded3 = AttributeCompression.octEncode(v3, scratchEncodeCart2);
result.x = 65536.0 * encoded3.x + encoded1;
result.y = 65536.0 * encoded3.y + encoded2;
return result;
};
/**
* Decodes three unit-length vectors in 'oct' encoding packed into a floating-point number to a normalized 3-component vector.
*
* @param {Cartesian2} packed The three oct-encoded unit length vectors stored as two floating-point number.
* @param {Cartesian3} v1 One decoded and normalized vector.
* @param {Cartesian3} v2 One decoded and normalized vector.
* @param {Cartesian3} v3 One decoded and normalized vector.
*/
AttributeCompression.octUnpack = function(packed, v1, v2, v3) {
if (!defined(packed)) {
throw new DeveloperError('packed is required.');
}
if (!defined(v1)) {
throw new DeveloperError('v1 is required.');
}
if (!defined(v2)) {
throw new DeveloperError('v2 is required.');
}
if (!defined(v3)) {
throw new DeveloperError('v3 is required.');
}
var temp = packed.x / 65536.0;
var x = Math.floor(temp);
var encodedFloat1 = (temp - x) * 65536.0;
temp = packed.y / 65536.0;
var y = Math.floor(temp);
var encodedFloat2 = (temp - y) * 65536.0;
AttributeCompression.octDecodeFloat(encodedFloat1, v1);
AttributeCompression.octDecodeFloat(encodedFloat2, v2);
AttributeCompression.octDecode(x, y, v3);
};
/**
* Pack texture coordinates into a single float. The texture coordinates will only preserve 12 bits of precision.
*
* @param {Cartesian2} textureCoordinates The texture coordinates to compress. Both coordinates must be in the range 0.0-1.0.
* @returns {Number} The packed texture coordinates.
*
*/
AttributeCompression.compressTextureCoordinates = function(textureCoordinates) {
if (!defined(textureCoordinates)) {
throw new DeveloperError('textureCoordinates is required.');
}
// Move x and y to the range 0-4095;
var x = (textureCoordinates.x * 4095.0) | 0;
var y = (textureCoordinates.y * 4095.0) | 0;
return 4096.0 * x + y;
};
/**
* Decompresses texture coordinates that were packed into a single float.
*
* @param {Number} compressed The compressed texture coordinates.
* @param {Cartesian2} result The decompressed texture coordinates.
* @returns {Cartesian2} The modified result parameter.
*
*/
AttributeCompression.decompressTextureCoordinates = function(compressed, result) {
if (!defined(compressed)) {
throw new DeveloperError('compressed is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var temp = compressed / 4096.0;
var xZeroTo4095 = Math.floor(temp);
result.x = xZeroTo4095 / 4095.0;
result.y = (compressed - xZeroTo4095 * 4096) / 4095;
return result;
};
return AttributeCompression;
});
/*global define*/
define('Core/WebGLConstants',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Enum containing WebGL Constant values by name.
* for use without an active WebGL context, or in cases where certain constants are unavailable using the WebGL context
* (For example, in [Safari 9]{@link https://github.com/AnalyticalGraphicsInc/cesium/issues/2989}).
*
* These match the constants from the [WebGL 1.0]{@link https://www.khronos.org/registry/webgl/specs/latest/1.0/}
* and [WebGL 2.0]{@link https://www.khronos.org/registry/webgl/specs/latest/2.0/}
* specifications.
*
* @exports WebGLConstants
*/
var WebGLConstants = {
DEPTH_BUFFER_BIT : 0x00000100,
STENCIL_BUFFER_BIT : 0x00000400,
COLOR_BUFFER_BIT : 0x00004000,
POINTS : 0x0000,
LINES : 0x0001,
LINE_LOOP : 0x0002,
LINE_STRIP : 0x0003,
TRIANGLES : 0x0004,
TRIANGLE_STRIP : 0x0005,
TRIANGLE_FAN : 0x0006,
ZERO : 0,
ONE : 1,
SRC_COLOR : 0x0300,
ONE_MINUS_SRC_COLOR : 0x0301,
SRC_ALPHA : 0x0302,
ONE_MINUS_SRC_ALPHA : 0x0303,
DST_ALPHA : 0x0304,
ONE_MINUS_DST_ALPHA : 0x0305,
DST_COLOR : 0x0306,
ONE_MINUS_DST_COLOR : 0x0307,
SRC_ALPHA_SATURATE : 0x0308,
FUNC_ADD : 0x8006,
BLEND_EQUATION : 0x8009,
BLEND_EQUATION_RGB : 0x8009, // same as BLEND_EQUATION
BLEND_EQUATION_ALPHA : 0x883D,
FUNC_SUBTRACT : 0x800A,
FUNC_REVERSE_SUBTRACT : 0x800B,
BLEND_DST_RGB : 0x80C8,
BLEND_SRC_RGB : 0x80C9,
BLEND_DST_ALPHA : 0x80CA,
BLEND_SRC_ALPHA : 0x80CB,
CONSTANT_COLOR : 0x8001,
ONE_MINUS_CONSTANT_COLOR : 0x8002,
CONSTANT_ALPHA : 0x8003,
ONE_MINUS_CONSTANT_ALPHA : 0x8004,
BLEND_COLOR : 0x8005,
ARRAY_BUFFER : 0x8892,
ELEMENT_ARRAY_BUFFER : 0x8893,
ARRAY_BUFFER_BINDING : 0x8894,
ELEMENT_ARRAY_BUFFER_BINDING : 0x8895,
STREAM_DRAW : 0x88E0,
STATIC_DRAW : 0x88E4,
DYNAMIC_DRAW : 0x88E8,
BUFFER_SIZE : 0x8764,
BUFFER_USAGE : 0x8765,
CURRENT_VERTEX_ATTRIB : 0x8626,
FRONT : 0x0404,
BACK : 0x0405,
FRONT_AND_BACK : 0x0408,
CULL_FACE : 0x0B44,
BLEND : 0x0BE2,
DITHER : 0x0BD0,
STENCIL_TEST : 0x0B90,
DEPTH_TEST : 0x0B71,
SCISSOR_TEST : 0x0C11,
POLYGON_OFFSET_FILL : 0x8037,
SAMPLE_ALPHA_TO_COVERAGE : 0x809E,
SAMPLE_COVERAGE : 0x80A0,
NO_ERROR : 0,
INVALID_ENUM : 0x0500,
INVALID_VALUE : 0x0501,
INVALID_OPERATION : 0x0502,
OUT_OF_MEMORY : 0x0505,
CW : 0x0900,
CCW : 0x0901,
LINE_WIDTH : 0x0B21,
ALIASED_POINT_SIZE_RANGE : 0x846D,
ALIASED_LINE_WIDTH_RANGE : 0x846E,
CULL_FACE_MODE : 0x0B45,
FRONT_FACE : 0x0B46,
DEPTH_RANGE : 0x0B70,
DEPTH_WRITEMASK : 0x0B72,
DEPTH_CLEAR_VALUE : 0x0B73,
DEPTH_FUNC : 0x0B74,
STENCIL_CLEAR_VALUE : 0x0B91,
STENCIL_FUNC : 0x0B92,
STENCIL_FAIL : 0x0B94,
STENCIL_PASS_DEPTH_FAIL : 0x0B95,
STENCIL_PASS_DEPTH_PASS : 0x0B96,
STENCIL_REF : 0x0B97,
STENCIL_VALUE_MASK : 0x0B93,
STENCIL_WRITEMASK : 0x0B98,
STENCIL_BACK_FUNC : 0x8800,
STENCIL_BACK_FAIL : 0x8801,
STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802,
STENCIL_BACK_PASS_DEPTH_PASS : 0x8803,
STENCIL_BACK_REF : 0x8CA3,
STENCIL_BACK_VALUE_MASK : 0x8CA4,
STENCIL_BACK_WRITEMASK : 0x8CA5,
VIEWPORT : 0x0BA2,
SCISSOR_BOX : 0x0C10,
COLOR_CLEAR_VALUE : 0x0C22,
COLOR_WRITEMASK : 0x0C23,
UNPACK_ALIGNMENT : 0x0CF5,
PACK_ALIGNMENT : 0x0D05,
MAX_TEXTURE_SIZE : 0x0D33,
MAX_VIEWPORT_DIMS : 0x0D3A,
SUBPIXEL_BITS : 0x0D50,
RED_BITS : 0x0D52,
GREEN_BITS : 0x0D53,
BLUE_BITS : 0x0D54,
ALPHA_BITS : 0x0D55,
DEPTH_BITS : 0x0D56,
STENCIL_BITS : 0x0D57,
POLYGON_OFFSET_UNITS : 0x2A00,
POLYGON_OFFSET_FACTOR : 0x8038,
TEXTURE_BINDING_2D : 0x8069,
SAMPLE_BUFFERS : 0x80A8,
SAMPLES : 0x80A9,
SAMPLE_COVERAGE_VALUE : 0x80AA,
SAMPLE_COVERAGE_INVERT : 0x80AB,
COMPRESSED_TEXTURE_FORMATS : 0x86A3,
DONT_CARE : 0x1100,
FASTEST : 0x1101,
NICEST : 0x1102,
GENERATE_MIPMAP_HINT : 0x8192,
BYTE : 0x1400,
UNSIGNED_BYTE : 0x1401,
SHORT : 0x1402,
UNSIGNED_SHORT : 0x1403,
INT : 0x1404,
UNSIGNED_INT : 0x1405,
FLOAT : 0x1406,
DEPTH_COMPONENT : 0x1902,
ALPHA : 0x1906,
RGB : 0x1907,
RGBA : 0x1908,
LUMINANCE : 0x1909,
LUMINANCE_ALPHA : 0x190A,
UNSIGNED_SHORT_4_4_4_4 : 0x8033,
UNSIGNED_SHORT_5_5_5_1 : 0x8034,
UNSIGNED_SHORT_5_6_5 : 0x8363,
FRAGMENT_SHADER : 0x8B30,
VERTEX_SHADER : 0x8B31,
MAX_VERTEX_ATTRIBS : 0x8869,
MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB,
MAX_VARYING_VECTORS : 0x8DFC,
MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D,
MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C,
MAX_TEXTURE_IMAGE_UNITS : 0x8872,
MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD,
SHADER_TYPE : 0x8B4F,
DELETE_STATUS : 0x8B80,
LINK_STATUS : 0x8B82,
VALIDATE_STATUS : 0x8B83,
ATTACHED_SHADERS : 0x8B85,
ACTIVE_UNIFORMS : 0x8B86,
ACTIVE_ATTRIBUTES : 0x8B89,
SHADING_LANGUAGE_VERSION : 0x8B8C,
CURRENT_PROGRAM : 0x8B8D,
NEVER : 0x0200,
LESS : 0x0201,
EQUAL : 0x0202,
LEQUAL : 0x0203,
GREATER : 0x0204,
NOTEQUAL : 0x0205,
GEQUAL : 0x0206,
ALWAYS : 0x0207,
KEEP : 0x1E00,
REPLACE : 0x1E01,
INCR : 0x1E02,
DECR : 0x1E03,
INVERT : 0x150A,
INCR_WRAP : 0x8507,
DECR_WRAP : 0x8508,
VENDOR : 0x1F00,
RENDERER : 0x1F01,
VERSION : 0x1F02,
NEAREST : 0x2600,
LINEAR : 0x2601,
NEAREST_MIPMAP_NEAREST : 0x2700,
LINEAR_MIPMAP_NEAREST : 0x2701,
NEAREST_MIPMAP_LINEAR : 0x2702,
LINEAR_MIPMAP_LINEAR : 0x2703,
TEXTURE_MAG_FILTER : 0x2800,
TEXTURE_MIN_FILTER : 0x2801,
TEXTURE_WRAP_S : 0x2802,
TEXTURE_WRAP_T : 0x2803,
TEXTURE_2D : 0x0DE1,
TEXTURE : 0x1702,
TEXTURE_CUBE_MAP : 0x8513,
TEXTURE_BINDING_CUBE_MAP : 0x8514,
TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515,
TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516,
TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517,
TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518,
TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519,
TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A,
MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C,
TEXTURE0 : 0x84C0,
TEXTURE1 : 0x84C1,
TEXTURE2 : 0x84C2,
TEXTURE3 : 0x84C3,
TEXTURE4 : 0x84C4,
TEXTURE5 : 0x84C5,
TEXTURE6 : 0x84C6,
TEXTURE7 : 0x84C7,
TEXTURE8 : 0x84C8,
TEXTURE9 : 0x84C9,
TEXTURE10 : 0x84CA,
TEXTURE11 : 0x84CB,
TEXTURE12 : 0x84CC,
TEXTURE13 : 0x84CD,
TEXTURE14 : 0x84CE,
TEXTURE15 : 0x84CF,
TEXTURE16 : 0x84D0,
TEXTURE17 : 0x84D1,
TEXTURE18 : 0x84D2,
TEXTURE19 : 0x84D3,
TEXTURE20 : 0x84D4,
TEXTURE21 : 0x84D5,
TEXTURE22 : 0x84D6,
TEXTURE23 : 0x84D7,
TEXTURE24 : 0x84D8,
TEXTURE25 : 0x84D9,
TEXTURE26 : 0x84DA,
TEXTURE27 : 0x84DB,
TEXTURE28 : 0x84DC,
TEXTURE29 : 0x84DD,
TEXTURE30 : 0x84DE,
TEXTURE31 : 0x84DF,
ACTIVE_TEXTURE : 0x84E0,
REPEAT : 0x2901,
CLAMP_TO_EDGE : 0x812F,
MIRRORED_REPEAT : 0x8370,
FLOAT_VEC2 : 0x8B50,
FLOAT_VEC3 : 0x8B51,
FLOAT_VEC4 : 0x8B52,
INT_VEC2 : 0x8B53,
INT_VEC3 : 0x8B54,
INT_VEC4 : 0x8B55,
BOOL : 0x8B56,
BOOL_VEC2 : 0x8B57,
BOOL_VEC3 : 0x8B58,
BOOL_VEC4 : 0x8B59,
FLOAT_MAT2 : 0x8B5A,
FLOAT_MAT3 : 0x8B5B,
FLOAT_MAT4 : 0x8B5C,
SAMPLER_2D : 0x8B5E,
SAMPLER_CUBE : 0x8B60,
VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622,
VERTEX_ATTRIB_ARRAY_SIZE : 0x8623,
VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624,
VERTEX_ATTRIB_ARRAY_TYPE : 0x8625,
VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A,
VERTEX_ATTRIB_ARRAY_POINTER : 0x8645,
VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F,
IMPLEMENTATION_COLOR_READ_TYPE : 0x8B9A,
IMPLEMENTATION_COLOR_READ_FORMAT : 0x8B9B,
COMPILE_STATUS : 0x8B81,
LOW_FLOAT : 0x8DF0,
MEDIUM_FLOAT : 0x8DF1,
HIGH_FLOAT : 0x8DF2,
LOW_INT : 0x8DF3,
MEDIUM_INT : 0x8DF4,
HIGH_INT : 0x8DF5,
FRAMEBUFFER : 0x8D40,
RENDERBUFFER : 0x8D41,
RGBA4 : 0x8056,
RGB5_A1 : 0x8057,
RGB565 : 0x8D62,
DEPTH_COMPONENT16 : 0x81A5,
STENCIL_INDEX : 0x1901,
STENCIL_INDEX8 : 0x8D48,
DEPTH_STENCIL : 0x84F9,
RENDERBUFFER_WIDTH : 0x8D42,
RENDERBUFFER_HEIGHT : 0x8D43,
RENDERBUFFER_INTERNAL_FORMAT : 0x8D44,
RENDERBUFFER_RED_SIZE : 0x8D50,
RENDERBUFFER_GREEN_SIZE : 0x8D51,
RENDERBUFFER_BLUE_SIZE : 0x8D52,
RENDERBUFFER_ALPHA_SIZE : 0x8D53,
RENDERBUFFER_DEPTH_SIZE : 0x8D54,
RENDERBUFFER_STENCIL_SIZE : 0x8D55,
FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0,
FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1,
FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2,
FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3,
COLOR_ATTACHMENT0 : 0x8CE0,
DEPTH_ATTACHMENT : 0x8D00,
STENCIL_ATTACHMENT : 0x8D20,
DEPTH_STENCIL_ATTACHMENT : 0x821A,
NONE : 0,
FRAMEBUFFER_COMPLETE : 0x8CD5,
FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6,
FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7,
FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9,
FRAMEBUFFER_UNSUPPORTED : 0x8CDD,
FRAMEBUFFER_BINDING : 0x8CA6,
RENDERBUFFER_BINDING : 0x8CA7,
MAX_RENDERBUFFER_SIZE : 0x84E8,
INVALID_FRAMEBUFFER_OPERATION : 0x0506,
UNPACK_FLIP_Y_WEBGL : 0x9240,
UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241,
CONTEXT_LOST_WEBGL : 0x9242,
UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243,
BROWSER_DEFAULT_WEBGL : 0x9244,
// Desktop OpenGL
DOUBLE : 0x140A,
// WebGL 2
READ_BUFFER : 0x0C02,
UNPACK_ROW_LENGTH : 0x0CF2,
UNPACK_SKIP_ROWS : 0x0CF3,
UNPACK_SKIP_PIXELS : 0x0CF4,
PACK_ROW_LENGTH : 0x0D02,
PACK_SKIP_ROWS : 0x0D03,
PACK_SKIP_PIXELS : 0x0D04,
COLOR : 0x1800,
DEPTH : 0x1801,
STENCIL : 0x1802,
RED : 0x1903,
RGB8 : 0x8051,
RGBA8 : 0x8058,
RGB10_A2 : 0x8059,
TEXTURE_BINDING_3D : 0x806A,
UNPACK_SKIP_IMAGES : 0x806D,
UNPACK_IMAGE_HEIGHT : 0x806E,
TEXTURE_3D : 0x806F,
TEXTURE_WRAP_R : 0x8072,
MAX_3D_TEXTURE_SIZE : 0x8073,
UNSIGNED_INT_2_10_10_10_REV : 0x8368,
MAX_ELEMENTS_VERTICES : 0x80E8,
MAX_ELEMENTS_INDICES : 0x80E9,
TEXTURE_MIN_LOD : 0x813A,
TEXTURE_MAX_LOD : 0x813B,
TEXTURE_BASE_LEVEL : 0x813C,
TEXTURE_MAX_LEVEL : 0x813D,
MIN : 0x8007,
MAX : 0x8008,
DEPTH_COMPONENT24 : 0x81A6,
MAX_TEXTURE_LOD_BIAS : 0x84FD,
TEXTURE_COMPARE_MODE : 0x884C,
TEXTURE_COMPARE_FUNC : 0x884D,
CURRENT_QUERY : 0x8865,
QUERY_RESULT : 0x8866,
QUERY_RESULT_AVAILABLE : 0x8867,
STREAM_READ : 0x88E1,
STREAM_COPY : 0x88E2,
STATIC_READ : 0x88E5,
STATIC_COPY : 0x88E6,
DYNAMIC_READ : 0x88E9,
DYNAMIC_COPY : 0x88EA,
MAX_DRAW_BUFFERS : 0x8824,
DRAW_BUFFER0 : 0x8825,
DRAW_BUFFER1 : 0x8826,
DRAW_BUFFER2 : 0x8827,
DRAW_BUFFER3 : 0x8828,
DRAW_BUFFER4 : 0x8829,
DRAW_BUFFER5 : 0x882A,
DRAW_BUFFER6 : 0x882B,
DRAW_BUFFER7 : 0x882C,
DRAW_BUFFER8 : 0x882D,
DRAW_BUFFER9 : 0x882E,
DRAW_BUFFER10 : 0x882F,
DRAW_BUFFER11 : 0x8830,
DRAW_BUFFER12 : 0x8831,
DRAW_BUFFER13 : 0x8832,
DRAW_BUFFER14 : 0x8833,
DRAW_BUFFER15 : 0x8834,
MAX_FRAGMENT_UNIFORM_COMPONENTS : 0x8B49,
MAX_VERTEX_UNIFORM_COMPONENTS : 0x8B4A,
SAMPLER_3D : 0x8B5F,
SAMPLER_2D_SHADOW : 0x8B62,
FRAGMENT_SHADER_DERIVATIVE_HINT : 0x8B8B,
PIXEL_PACK_BUFFER : 0x88EB,
PIXEL_UNPACK_BUFFER : 0x88EC,
PIXEL_PACK_BUFFER_BINDING : 0x88ED,
PIXEL_UNPACK_BUFFER_BINDING : 0x88EF,
FLOAT_MAT2x3 : 0x8B65,
FLOAT_MAT2x4 : 0x8B66,
FLOAT_MAT3x2 : 0x8B67,
FLOAT_MAT3x4 : 0x8B68,
FLOAT_MAT4x2 : 0x8B69,
FLOAT_MAT4x3 : 0x8B6A,
SRGB : 0x8C40,
SRGB8 : 0x8C41,
SRGB8_ALPHA8 : 0x8C43,
COMPARE_REF_TO_TEXTURE : 0x884E,
RGBA32F : 0x8814,
RGB32F : 0x8815,
RGBA16F : 0x881A,
RGB16F : 0x881B,
VERTEX_ATTRIB_ARRAY_INTEGER : 0x88FD,
MAX_ARRAY_TEXTURE_LAYERS : 0x88FF,
MIN_PROGRAM_TEXEL_OFFSET : 0x8904,
MAX_PROGRAM_TEXEL_OFFSET : 0x8905,
MAX_VARYING_COMPONENTS : 0x8B4B,
TEXTURE_2D_ARRAY : 0x8C1A,
TEXTURE_BINDING_2D_ARRAY : 0x8C1D,
R11F_G11F_B10F : 0x8C3A,
UNSIGNED_INT_10F_11F_11F_REV : 0x8C3B,
RGB9_E5 : 0x8C3D,
UNSIGNED_INT_5_9_9_9_REV : 0x8C3E,
TRANSFORM_FEEDBACK_BUFFER_MODE : 0x8C7F,
MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS : 0x8C80,
TRANSFORM_FEEDBACK_VARYINGS : 0x8C83,
TRANSFORM_FEEDBACK_BUFFER_START : 0x8C84,
TRANSFORM_FEEDBACK_BUFFER_SIZE : 0x8C85,
TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN : 0x8C88,
RASTERIZER_DISCARD : 0x8C89,
MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS : 0x8C8A,
MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS : 0x8C8B,
INTERLEAVED_ATTRIBS : 0x8C8C,
SEPARATE_ATTRIBS : 0x8C8D,
TRANSFORM_FEEDBACK_BUFFER : 0x8C8E,
TRANSFORM_FEEDBACK_BUFFER_BINDING : 0x8C8F,
RGBA32UI : 0x8D70,
RGB32UI : 0x8D71,
RGBA16UI : 0x8D76,
RGB16UI : 0x8D77,
RGBA8UI : 0x8D7C,
RGB8UI : 0x8D7D,
RGBA32I : 0x8D82,
RGB32I : 0x8D83,
RGBA16I : 0x8D88,
RGB16I : 0x8D89,
RGBA8I : 0x8D8E,
RGB8I : 0x8D8F,
RED_INTEGER : 0x8D94,
RGB_INTEGER : 0x8D98,
RGBA_INTEGER : 0x8D99,
SAMPLER_2D_ARRAY : 0x8DC1,
SAMPLER_2D_ARRAY_SHADOW : 0x8DC4,
SAMPLER_CUBE_SHADOW : 0x8DC5,
UNSIGNED_INT_VEC2 : 0x8DC6,
UNSIGNED_INT_VEC3 : 0x8DC7,
UNSIGNED_INT_VEC4 : 0x8DC8,
INT_SAMPLER_2D : 0x8DCA,
INT_SAMPLER_3D : 0x8DCB,
INT_SAMPLER_CUBE : 0x8DCC,
INT_SAMPLER_2D_ARRAY : 0x8DCF,
UNSIGNED_INT_SAMPLER_2D : 0x8DD2,
UNSIGNED_INT_SAMPLER_3D : 0x8DD3,
UNSIGNED_INT_SAMPLER_CUBE : 0x8DD4,
UNSIGNED_INT_SAMPLER_2D_ARRAY : 0x8DD7,
DEPTH_COMPONENT32F : 0x8CAC,
DEPTH32F_STENCIL8 : 0x8CAD,
FLOAT_32_UNSIGNED_INT_24_8_REV : 0x8DAD,
FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING : 0x8210,
FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE : 0x8211,
FRAMEBUFFER_ATTACHMENT_RED_SIZE : 0x8212,
FRAMEBUFFER_ATTACHMENT_GREEN_SIZE : 0x8213,
FRAMEBUFFER_ATTACHMENT_BLUE_SIZE : 0x8214,
FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE : 0x8215,
FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE : 0x8216,
FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE : 0x8217,
FRAMEBUFFER_DEFAULT : 0x8218,
UNSIGNED_INT_24_8 : 0x84FA,
DEPTH24_STENCIL8 : 0x88F0,
UNSIGNED_NORMALIZED : 0x8C17,
DRAW_FRAMEBUFFER_BINDING : 0x8CA6, // Same as FRAMEBUFFER_BINDING
READ_FRAMEBUFFER : 0x8CA8,
DRAW_FRAMEBUFFER : 0x8CA9,
READ_FRAMEBUFFER_BINDING : 0x8CAA,
RENDERBUFFER_SAMPLES : 0x8CAB,
FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER : 0x8CD4,
MAX_COLOR_ATTACHMENTS : 0x8CDF,
COLOR_ATTACHMENT1 : 0x8CE1,
COLOR_ATTACHMENT2 : 0x8CE2,
COLOR_ATTACHMENT3 : 0x8CE3,
COLOR_ATTACHMENT4 : 0x8CE4,
COLOR_ATTACHMENT5 : 0x8CE5,
COLOR_ATTACHMENT6 : 0x8CE6,
COLOR_ATTACHMENT7 : 0x8CE7,
COLOR_ATTACHMENT8 : 0x8CE8,
COLOR_ATTACHMENT9 : 0x8CE9,
COLOR_ATTACHMENT10 : 0x8CEA,
COLOR_ATTACHMENT11 : 0x8CEB,
COLOR_ATTACHMENT12 : 0x8CEC,
COLOR_ATTACHMENT13 : 0x8CED,
COLOR_ATTACHMENT14 : 0x8CEE,
COLOR_ATTACHMENT15 : 0x8CEF,
FRAMEBUFFER_INCOMPLETE_MULTISAMPLE : 0x8D56,
MAX_SAMPLES : 0x8D57,
HALF_FLOAT : 0x140B,
RG : 0x8227,
RG_INTEGER : 0x8228,
R8 : 0x8229,
RG8 : 0x822B,
R16F : 0x822D,
R32F : 0x822E,
RG16F : 0x822F,
RG32F : 0x8230,
R8I : 0x8231,
R8UI : 0x8232,
R16I : 0x8233,
R16UI : 0x8234,
R32I : 0x8235,
R32UI : 0x8236,
RG8I : 0x8237,
RG8UI : 0x8238,
RG16I : 0x8239,
RG16UI : 0x823A,
RG32I : 0x823B,
RG32UI : 0x823C,
VERTEX_ARRAY_BINDING : 0x85B5,
R8_SNORM : 0x8F94,
RG8_SNORM : 0x8F95,
RGB8_SNORM : 0x8F96,
RGBA8_SNORM : 0x8F97,
SIGNED_NORMALIZED : 0x8F9C,
COPY_READ_BUFFER : 0x8F36,
COPY_WRITE_BUFFER : 0x8F37,
COPY_READ_BUFFER_BINDING : 0x8F36, // Same as COPY_READ_BUFFER
COPY_WRITE_BUFFER_BINDING : 0x8F37, // Same as COPY_WRITE_BUFFER
UNIFORM_BUFFER : 0x8A11,
UNIFORM_BUFFER_BINDING : 0x8A28,
UNIFORM_BUFFER_START : 0x8A29,
UNIFORM_BUFFER_SIZE : 0x8A2A,
MAX_VERTEX_UNIFORM_BLOCKS : 0x8A2B,
MAX_FRAGMENT_UNIFORM_BLOCKS : 0x8A2D,
MAX_COMBINED_UNIFORM_BLOCKS : 0x8A2E,
MAX_UNIFORM_BUFFER_BINDINGS : 0x8A2F,
MAX_UNIFORM_BLOCK_SIZE : 0x8A30,
MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS : 0x8A31,
MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS : 0x8A33,
UNIFORM_BUFFER_OFFSET_ALIGNMENT : 0x8A34,
ACTIVE_UNIFORM_BLOCKS : 0x8A36,
UNIFORM_TYPE : 0x8A37,
UNIFORM_SIZE : 0x8A38,
UNIFORM_BLOCK_INDEX : 0x8A3A,
UNIFORM_OFFSET : 0x8A3B,
UNIFORM_ARRAY_STRIDE : 0x8A3C,
UNIFORM_MATRIX_STRIDE : 0x8A3D,
UNIFORM_IS_ROW_MAJOR : 0x8A3E,
UNIFORM_BLOCK_BINDING : 0x8A3F,
UNIFORM_BLOCK_DATA_SIZE : 0x8A40,
UNIFORM_BLOCK_ACTIVE_UNIFORMS : 0x8A42,
UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES : 0x8A43,
UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER : 0x8A44,
UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER : 0x8A46,
INVALID_INDEX : 0xFFFFFFFF,
MAX_VERTEX_OUTPUT_COMPONENTS : 0x9122,
MAX_FRAGMENT_INPUT_COMPONENTS : 0x9125,
MAX_SERVER_WAIT_TIMEOUT : 0x9111,
OBJECT_TYPE : 0x9112,
SYNC_CONDITION : 0x9113,
SYNC_STATUS : 0x9114,
SYNC_FLAGS : 0x9115,
SYNC_FENCE : 0x9116,
SYNC_GPU_COMMANDS_COMPLETE : 0x9117,
UNSIGNALED : 0x9118,
SIGNALED : 0x9119,
ALREADY_SIGNALED : 0x911A,
TIMEOUT_EXPIRED : 0x911B,
CONDITION_SATISFIED : 0x911C,
WAIT_FAILED : 0x911D,
SYNC_FLUSH_COMMANDS_BIT : 0x00000001,
VERTEX_ATTRIB_ARRAY_DIVISOR : 0x88FE,
ANY_SAMPLES_PASSED : 0x8C2F,
ANY_SAMPLES_PASSED_CONSERVATIVE : 0x8D6A,
SAMPLER_BINDING : 0x8919,
RGB10_A2UI : 0x906F,
INT_2_10_10_10_REV : 0x8D9F,
TRANSFORM_FEEDBACK : 0x8E22,
TRANSFORM_FEEDBACK_PAUSED : 0x8E23,
TRANSFORM_FEEDBACK_ACTIVE : 0x8E24,
TRANSFORM_FEEDBACK_BINDING : 0x8E25,
COMPRESSED_R11_EAC : 0x9270,
COMPRESSED_SIGNED_R11_EAC : 0x9271,
COMPRESSED_RG11_EAC : 0x9272,
COMPRESSED_SIGNED_RG11_EAC : 0x9273,
COMPRESSED_RGB8_ETC2 : 0x9274,
COMPRESSED_SRGB8_ETC2 : 0x9275,
COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 : 0x9276,
COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 : 0x9277,
COMPRESSED_RGBA8_ETC2_EAC : 0x9278,
COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : 0x9279,
TEXTURE_IMMUTABLE_FORMAT : 0x912F,
MAX_ELEMENT_INDEX : 0x8D6B,
TEXTURE_IMMUTABLE_LEVELS : 0x82DF
};
return freezeObject(WebGLConstants);
});
/*global define*/
define('Core/ComponentDatatype',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./FeatureDetection',
'./freezeObject',
'./WebGLConstants'
], function(
defaultValue,
defined,
DeveloperError,
FeatureDetection,
freezeObject,
WebGLConstants) {
'use strict';
// Bail out if the browser doesn't support typed arrays, to prevent the setup function
// from failing, since we won't be able to create a WebGL context anyway.
if (!FeatureDetection.supportsTypedArrays()) {
return {};
}
/**
* WebGL component datatypes. Components are intrinsics,
* which form attributes, which form vertices.
*
* @exports ComponentDatatype
*/
var ComponentDatatype = {
/**
* 8-bit signed byte corresponding to gl.BYTE
and the type
* of an element in Int8Array
.
*
* @type {Number}
* @constant
*/
BYTE : WebGLConstants.BYTE,
/**
* 8-bit unsigned byte corresponding to UNSIGNED_BYTE
and the type
* of an element in Uint8Array
.
*
* @type {Number}
* @constant
*/
UNSIGNED_BYTE : WebGLConstants.UNSIGNED_BYTE,
/**
* 16-bit signed short corresponding to SHORT
and the type
* of an element in Int16Array
.
*
* @type {Number}
* @constant
*/
SHORT : WebGLConstants.SHORT,
/**
* 16-bit unsigned short corresponding to UNSIGNED_SHORT
and the type
* of an element in Uint16Array
.
*
* @type {Number}
* @constant
*/
UNSIGNED_SHORT : WebGLConstants.UNSIGNED_SHORT,
/**
* 32-bit signed int corresponding to INT
and the type
* of an element in Int32Array
.
*
* @memberOf ComponentDatatype
*
* @type {Number}
* @constant
*/
INT : WebGLConstants.INT,
/**
* 32-bit unsigned int corresponding to UNSIGNED_INT
and the type
* of an element in Uint32Array
.
*
* @memberOf ComponentDatatype
*
* @type {Number}
* @constant
*/
UNSIGNED_INT : WebGLConstants.UNSIGNED_INT,
/**
* 32-bit floating-point corresponding to FLOAT
and the type
* of an element in Float32Array
.
*
* @type {Number}
* @constant
*/
FLOAT : WebGLConstants.FLOAT,
/**
* 64-bit floating-point corresponding to gl.DOUBLE
(in Desktop OpenGL;
* this is not supported in WebGL, and is emulated in Cesium via {@link GeometryPipeline.encodeAttribute})
* and the type of an element in Float64Array
.
*
* @memberOf ComponentDatatype
*
* @type {Number}
* @constant
* @default 0x140A
*/
DOUBLE : WebGLConstants.DOUBLE
};
/**
* Returns the size, in bytes, of the corresponding datatype.
*
* @param {ComponentDatatype} componentDatatype The component datatype to get the size of.
* @returns {Number} The size in bytes.
*
* @exception {DeveloperError} componentDatatype is not a valid value.
*
* @example
* // Returns Int8Array.BYTES_PER_ELEMENT
* var size = Cesium.ComponentDatatype.getSizeInBytes(Cesium.ComponentDatatype.BYTE);
*/
ComponentDatatype.getSizeInBytes = function(componentDatatype){
if (!defined(componentDatatype)) {
throw new DeveloperError('value is required.');
}
switch (componentDatatype) {
case ComponentDatatype.BYTE:
return Int8Array.BYTES_PER_ELEMENT;
case ComponentDatatype.UNSIGNED_BYTE:
return Uint8Array.BYTES_PER_ELEMENT;
case ComponentDatatype.SHORT:
return Int16Array.BYTES_PER_ELEMENT;
case ComponentDatatype.UNSIGNED_SHORT:
return Uint16Array.BYTES_PER_ELEMENT;
case ComponentDatatype.INT:
return Int32Array.BYTES_PER_ELEMENT;
case ComponentDatatype.UNSIGNED_INT:
return Uint32Array.BYTES_PER_ELEMENT;
case ComponentDatatype.FLOAT:
return Float32Array.BYTES_PER_ELEMENT;
case ComponentDatatype.DOUBLE:
return Float64Array.BYTES_PER_ELEMENT;
default:
throw new DeveloperError('componentDatatype is not a valid value.');
}
};
/**
* Gets the {@link ComponentDatatype} for the provided TypedArray instance.
*
* @param {TypedArray} array The typed array.
* @returns {ComponentDatatype} The ComponentDatatype for the provided array, or undefined if the array is not a TypedArray.
*/
ComponentDatatype.fromTypedArray = function(array) {
if (array instanceof Int8Array) {
return ComponentDatatype.BYTE;
}
if (array instanceof Uint8Array) {
return ComponentDatatype.UNSIGNED_BYTE;
}
if (array instanceof Int16Array) {
return ComponentDatatype.SHORT;
}
if (array instanceof Uint16Array) {
return ComponentDatatype.UNSIGNED_SHORT;
}
if (array instanceof Int32Array) {
return ComponentDatatype.INT;
}
if (array instanceof Uint32Array) {
return ComponentDatatype.UNSIGNED_INT;
}
if (array instanceof Float32Array) {
return ComponentDatatype.FLOAT;
}
if (array instanceof Float64Array) {
return ComponentDatatype.DOUBLE;
}
};
/**
* Validates that the provided component datatype is a valid {@link ComponentDatatype}
*
* @param {ComponentDatatype} componentDatatype The component datatype to validate.
* @returns {Boolean} true
if the provided component datatype is a valid value; otherwise, false
.
*
* @example
* if (!Cesium.ComponentDatatype.validate(componentDatatype)) {
* throw new Cesium.DeveloperError('componentDatatype must be a valid value.');
* }
*/
ComponentDatatype.validate = function(componentDatatype) {
return defined(componentDatatype) &&
(componentDatatype === ComponentDatatype.BYTE ||
componentDatatype === ComponentDatatype.UNSIGNED_BYTE ||
componentDatatype === ComponentDatatype.SHORT ||
componentDatatype === ComponentDatatype.UNSIGNED_SHORT ||
componentDatatype === ComponentDatatype.INT ||
componentDatatype === ComponentDatatype.UNSIGNED_INT ||
componentDatatype === ComponentDatatype.FLOAT ||
componentDatatype === ComponentDatatype.DOUBLE);
};
/**
* Creates a typed array corresponding to component data type.
*
* @param {ComponentDatatype} componentDatatype The component data type.
* @param {Number|Array} valuesOrLength The length of the array to create or an array.
* @returns {Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} A typed array.
*
* @exception {DeveloperError} componentDatatype is not a valid value.
*
* @example
* // creates a Float32Array with length of 100
* var typedArray = Cesium.ComponentDatatype.createTypedArray(Cesium.ComponentDatatype.FLOAT, 100);
*/
ComponentDatatype.createTypedArray = function(componentDatatype, valuesOrLength) {
if (!defined(componentDatatype)) {
throw new DeveloperError('componentDatatype is required.');
}
if (!defined(valuesOrLength)) {
throw new DeveloperError('valuesOrLength is required.');
}
switch (componentDatatype) {
case ComponentDatatype.BYTE:
return new Int8Array(valuesOrLength);
case ComponentDatatype.UNSIGNED_BYTE:
return new Uint8Array(valuesOrLength);
case ComponentDatatype.SHORT:
return new Int16Array(valuesOrLength);
case ComponentDatatype.UNSIGNED_SHORT:
return new Uint16Array(valuesOrLength);
case ComponentDatatype.INT:
return new Int32Array(valuesOrLength);
case ComponentDatatype.UNSIGNED_INT:
return new Uint32Array(valuesOrLength);
case ComponentDatatype.FLOAT:
return new Float32Array(valuesOrLength);
case ComponentDatatype.DOUBLE:
return new Float64Array(valuesOrLength);
default:
throw new DeveloperError('componentDatatype is not a valid value.');
}
};
/**
* Creates a typed view of an array of bytes.
*
* @param {ComponentDatatype} componentDatatype The type of the view to create.
* @param {ArrayBuffer} buffer The buffer storage to use for the view.
* @param {Number} [byteOffset] The offset, in bytes, to the first element in the view.
* @param {Number} [length] The number of elements in the view.
* @returns {Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} A typed array view of the buffer.
*
* @exception {DeveloperError} componentDatatype is not a valid value.
*/
ComponentDatatype.createArrayBufferView = function(componentDatatype, buffer, byteOffset, length) {
if (!defined(componentDatatype)) {
throw new DeveloperError('componentDatatype is required.');
}
if (!defined(buffer)) {
throw new DeveloperError('buffer is required.');
}
byteOffset = defaultValue(byteOffset, 0);
length = defaultValue(length, (buffer.byteLength - byteOffset) / ComponentDatatype.getSizeInBytes(componentDatatype));
switch (componentDatatype) {
case ComponentDatatype.BYTE:
return new Int8Array(buffer, byteOffset, length);
case ComponentDatatype.UNSIGNED_BYTE:
return new Uint8Array(buffer, byteOffset, length);
case ComponentDatatype.SHORT:
return new Int16Array(buffer, byteOffset, length);
case ComponentDatatype.UNSIGNED_SHORT:
return new Uint16Array(buffer, byteOffset, length);
case ComponentDatatype.INT:
return new Int32Array(buffer, byteOffset, length);
case ComponentDatatype.UNSIGNED_INT:
return new Uint32Array(buffer, byteOffset, length);
case ComponentDatatype.FLOAT:
return new Float32Array(buffer, byteOffset, length);
case ComponentDatatype.DOUBLE:
return new Float64Array(buffer, byteOffset, length);
default:
throw new DeveloperError('componentDatatype is not a valid value.');
}
};
/**
* Get the ComponentDatatype from its name.
*
* @param {String} name The name of the ComponentDatatype.
* @returns {ComponentDatatype} The ComponentDatatype.
*
* @exception {DeveloperError} name is not a valid value.
*/
ComponentDatatype.fromName = function(name) {
switch (name) {
case 'BYTE':
return ComponentDatatype.BYTE;
case 'UNSIGNED_BYTE':
return ComponentDatatype.UNSIGNED_BYTE;
case 'SHORT':
return ComponentDatatype.SHORT;
case 'UNSIGNED_SHORT':
return ComponentDatatype.UNSIGNED_SHORT;
case 'INT':
return ComponentDatatype.INT;
case 'UNSIGNED_INT':
return ComponentDatatype.UNSIGNED_INT;
case 'FLOAT':
return ComponentDatatype.FLOAT;
case 'DOUBLE':
return ComponentDatatype.DOUBLE;
default:
throw new DeveloperError('name is not a valid value.');
}
};
return freezeObject(ComponentDatatype);
});
/*global define*/
define('Core/TerrainQuantization',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* This enumerated type is used to determine how the vertices of the terrain mesh are compressed.
*
* @exports TerrainQuantization
*
* @private
*/
var TerrainQuantization = {
/**
* The vertices are not compressed.
*
* @type {Number}
* @constant
*/
NONE : 0,
/**
* The vertices are compressed to 12 bits.
*
* @type {Number}
* @constant
*/
BITS12 : 1
};
return freezeObject(TerrainQuantization);
});
/*global define*/
define('Core/TerrainEncoding',[
'./AttributeCompression',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./Math',
'./Matrix4',
'./TerrainQuantization'
], function(
AttributeCompression,
Cartesian2,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
CesiumMath,
Matrix4,
TerrainQuantization) {
'use strict';
var cartesian3Scratch = new Cartesian3();
var cartesian3DimScratch = new Cartesian3();
var cartesian2Scratch = new Cartesian2();
var matrix4Scratch = new Matrix4();
var matrix4Scratch2 = new Matrix4();
var SHIFT_LEFT_12 = Math.pow(2.0, 12.0);
/**
* Data used to quantize and pack the terrain mesh. The position can be unpacked for picking and all attributes
* are unpacked in the vertex shader.
*
* @alias TerrainEncoding
* @constructor
*
* @param {AxisAlignedBoundingBox} axisAlignedBoundingBox The bounds of the tile in the east-north-up coordinates at the tiles center.
* @param {Number} minimumHeight The minimum height.
* @param {Number} maximumHeight The maximum height.
* @param {Matrix4} fromENU The east-north-up to fixed frame matrix at the center of the terrain mesh.
* @param {Boolean} hasVertexNormals If the mesh has vertex normals.
* @param {Boolean} [hasWebMercatorT=false] true if the terrain data includes a Web Mercator texture coordinate; otherwise, false.
*
* @private
*/
function TerrainEncoding(axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals, hasWebMercatorT) {
var quantization;
var center;
var toENU;
var matrix;
if (defined(axisAlignedBoundingBox) && defined(minimumHeight) && defined(maximumHeight) && defined(fromENU)) {
var minimum = axisAlignedBoundingBox.minimum;
var maximum = axisAlignedBoundingBox.maximum;
var dimensions = Cartesian3.subtract(maximum, minimum, cartesian3DimScratch);
var hDim = maximumHeight - minimumHeight;
var maxDim = Math.max(Cartesian3.maximumComponent(dimensions), hDim);
if (maxDim < SHIFT_LEFT_12 - 1.0) {
quantization = TerrainQuantization.BITS12;
} else {
quantization = TerrainQuantization.NONE;
}
center = axisAlignedBoundingBox.center;
toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
var translation = Cartesian3.negate(minimum, cartesian3Scratch);
Matrix4.multiply(Matrix4.fromTranslation(translation, matrix4Scratch), toENU, toENU);
var scale = cartesian3Scratch;
scale.x = 1.0 / dimensions.x;
scale.y = 1.0 / dimensions.y;
scale.z = 1.0 / dimensions.z;
Matrix4.multiply(Matrix4.fromScale(scale, matrix4Scratch), toENU, toENU);
matrix = Matrix4.clone(fromENU);
Matrix4.setTranslation(matrix, Cartesian3.ZERO, matrix);
fromENU = Matrix4.clone(fromENU, new Matrix4());
var translationMatrix = Matrix4.fromTranslation(minimum, matrix4Scratch);
var scaleMatrix = Matrix4.fromScale(dimensions, matrix4Scratch2);
var st = Matrix4.multiply(translationMatrix, scaleMatrix,matrix4Scratch);
Matrix4.multiply(fromENU, st, fromENU);
Matrix4.multiply(matrix, st, matrix);
}
/**
* How the vertices of the mesh were compressed.
* @type {TerrainQuantization}
*/
this.quantization = quantization;
/**
* The minimum height of the tile including the skirts.
* @type {Number}
*/
this.minimumHeight = minimumHeight;
/**
* The maximum height of the tile.
* @type {Number}
*/
this.maximumHeight = maximumHeight;
/**
* The center of the tile.
* @type {Cartesian3}
*/
this.center = center;
/**
* A matrix that takes a vertex from the tile, transforms it to east-north-up at the center and scales
* it so each component is in the [0, 1] range.
* @type {Matrix4}
*/
this.toScaledENU = toENU;
/**
* A matrix that restores a vertex transformed with toScaledENU back to the earth fixed reference frame
* @type {Matrix4}
*/
this.fromScaledENU = fromENU;
/**
* The matrix used to decompress the terrain vertices in the shader for RTE rendering.
* @type {Matrix4}
*/
this.matrix = matrix;
/**
* The terrain mesh contains normals.
* @type {Boolean}
*/
this.hasVertexNormals = hasVertexNormals;
/**
* The terrain mesh contains a vertical texture coordinate following the Web Mercator projection.
* @type {Boolean}
*/
this.hasWebMercatorT = defaultValue(hasWebMercatorT, false);
}
TerrainEncoding.prototype.encode = function(vertexBuffer, bufferIndex, position, uv, height, normalToPack, webMercatorT) {
var u = uv.x;
var v = uv.y;
if (this.quantization === TerrainQuantization.BITS12) {
position = Matrix4.multiplyByPoint(this.toScaledENU, position, cartesian3Scratch);
position.x = CesiumMath.clamp(position.x, 0.0, 1.0);
position.y = CesiumMath.clamp(position.y, 0.0, 1.0);
position.z = CesiumMath.clamp(position.z, 0.0, 1.0);
var hDim = this.maximumHeight - this.minimumHeight;
var h = CesiumMath.clamp((height - this.minimumHeight) / hDim, 0.0, 1.0);
Cartesian2.fromElements(position.x, position.y, cartesian2Scratch);
var compressed0 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);
Cartesian2.fromElements(position.z, h, cartesian2Scratch);
var compressed1 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);
Cartesian2.fromElements(u, v, cartesian2Scratch);
var compressed2 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);
vertexBuffer[bufferIndex++] = compressed0;
vertexBuffer[bufferIndex++] = compressed1;
vertexBuffer[bufferIndex++] = compressed2;
if (this.hasWebMercatorT) {
Cartesian2.fromElements(webMercatorT, 0.0, cartesian2Scratch);
var compressed3 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);
vertexBuffer[bufferIndex++] = compressed3;
}
} else {
Cartesian3.subtract(position, this.center, cartesian3Scratch);
vertexBuffer[bufferIndex++] = cartesian3Scratch.x;
vertexBuffer[bufferIndex++] = cartesian3Scratch.y;
vertexBuffer[bufferIndex++] = cartesian3Scratch.z;
vertexBuffer[bufferIndex++] = height;
vertexBuffer[bufferIndex++] = u;
vertexBuffer[bufferIndex++] = v;
if (this.hasWebMercatorT) {
vertexBuffer[bufferIndex++] = webMercatorT;
}
}
if (this.hasVertexNormals) {
vertexBuffer[bufferIndex++] = AttributeCompression.octPackFloat(normalToPack);
}
return bufferIndex;
};
TerrainEncoding.prototype.decodePosition = function(buffer, index, result) {
if (!defined(result)) {
result = new Cartesian3();
}
index *= this.getStride();
if (this.quantization === TerrainQuantization.BITS12) {
var xy = AttributeCompression.decompressTextureCoordinates(buffer[index], cartesian2Scratch);
result.x = xy.x;
result.y = xy.y;
var zh = AttributeCompression.decompressTextureCoordinates(buffer[index + 1], cartesian2Scratch);
result.z = zh.x;
return Matrix4.multiplyByPoint(this.fromScaledENU, result, result);
}
result.x = buffer[index];
result.y = buffer[index + 1];
result.z = buffer[index + 2];
return Cartesian3.add(result, this.center, result);
};
TerrainEncoding.prototype.decodeTextureCoordinates = function(buffer, index, result) {
if (!defined(result)) {
result = new Cartesian2();
}
index *= this.getStride();
if (this.quantization === TerrainQuantization.BITS12) {
return AttributeCompression.decompressTextureCoordinates(buffer[index + 2], result);
}
return Cartesian2.fromElements(buffer[index + 4], buffer[index + 5], result);
};
TerrainEncoding.prototype.decodeHeight = function(buffer, index) {
index *= this.getStride();
if (this.quantization === TerrainQuantization.BITS12) {
var zh = AttributeCompression.decompressTextureCoordinates(buffer[index + 1], cartesian2Scratch);
return zh.y * (this.maximumHeight - this.minimumHeight) + this.minimumHeight;
}
return buffer[index + 3];
};
TerrainEncoding.prototype.getOctEncodedNormal = function(buffer, index, result) {
var stride = this.getStride();
index = (index + 1) * stride - 1;
var temp = buffer[index] / 256.0;
var x = Math.floor(temp);
var y = (temp - x) * 256.0;
return Cartesian2.fromElements(x, y, result);
};
TerrainEncoding.prototype.getStride = function() {
var vertexStride;
switch (this.quantization) {
case TerrainQuantization.BITS12:
vertexStride = 3;
break;
default:
vertexStride = 6;
}
if (this.hasWebMercatorT) {
++vertexStride;
}
if (this.hasVertexNormals) {
++vertexStride;
}
return vertexStride;
};
var attributesNone = {
position3DAndHeight : 0,
textureCoordAndEncodedNormals : 1
};
var attributes = {
compressed0 : 0,
compressed1 : 1
};
TerrainEncoding.prototype.getAttributes = function(buffer) {
var datatype = ComponentDatatype.FLOAT;
var sizeInBytes = ComponentDatatype.getSizeInBytes(datatype);
var stride;
if (this.quantization === TerrainQuantization.NONE) {
var position3DAndHeightLength = 4;
var numTexCoordComponents = 2;
if (this.hasWebMercatorT) {
++numTexCoordComponents;
}
if (this.hasVertexNormals) {
++numTexCoordComponents;
}
stride = (position3DAndHeightLength + numTexCoordComponents) * sizeInBytes;
return [{
index : attributesNone.position3DAndHeight,
vertexBuffer : buffer,
componentDatatype : datatype,
componentsPerAttribute : position3DAndHeightLength,
offsetInBytes : 0,
strideInBytes : stride
}, {
index : attributesNone.textureCoordAndEncodedNormals,
vertexBuffer : buffer,
componentDatatype : datatype,
componentsPerAttribute : numTexCoordComponents,
offsetInBytes : position3DAndHeightLength * sizeInBytes,
strideInBytes : stride
}];
}
var numCompressed0 = 3;
var numCompressed1 = 0;
if (this.hasWebMercatorT || this.hasVertexNormals) {
++numCompressed0;
}
if (this.hasWebMercatorT && this.hasVertexNormals) {
++numCompressed1;
stride = (numCompressed0 + numCompressed1) * sizeInBytes;
return [{
index : attributes.compressed0,
vertexBuffer : buffer,
componentDatatype : datatype,
componentsPerAttribute : numCompressed0,
offsetInBytes : 0,
strideInBytes : stride
}, {
index : attributes.compressed1,
vertexBuffer : buffer,
componentDatatype : datatype,
componentsPerAttribute : numCompressed1,
offsetInBytes : numCompressed0 * sizeInBytes,
strideInBytes : stride
}];
} else {
return [{
index : attributes.compressed0,
vertexBuffer : buffer,
componentDatatype : datatype,
componentsPerAttribute : numCompressed0
}];
}
};
TerrainEncoding.prototype.getAttributeLocations = function() {
if (this.quantization === TerrainQuantization.NONE) {
return attributesNone;
} else {
return attributes;
}
};
TerrainEncoding.clone = function(encoding, result) {
if (!defined(result)) {
result = new TerrainEncoding();
}
result.quantization = encoding.quantization;
result.minimumHeight = encoding.minimumHeight;
result.maximumHeight = encoding.maximumHeight;
result.center = Cartesian3.clone(encoding.center);
result.toScaledENU = Matrix4.clone(encoding.toScaledENU);
result.fromScaledENU = Matrix4.clone(encoding.fromScaledENU);
result.matrix = Matrix4.clone(encoding.matrix);
result.hasVertexNormals = encoding.hasVertexNormals;
result.hasWebMercatorT = encoding.hasWebMercatorT;
return result;
};
return TerrainEncoding;
});
/*global define*/
define('Core/WebMercatorProjection',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Math'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
CesiumMath) {
'use strict';
/**
* The map projection used by Google Maps, Bing Maps, and most of ArcGIS Online, EPSG:3857. This
* projection use longitude and latitude expressed with the WGS84 and transforms them to Mercator using
* the spherical (rather than ellipsoidal) equations.
*
* @alias WebMercatorProjection
* @constructor
*
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid.
*
* @see GeographicProjection
*/
function WebMercatorProjection(ellipsoid) {
this._ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
this._semimajorAxis = this._ellipsoid.maximumRadius;
this._oneOverSemimajorAxis = 1.0 / this._semimajorAxis;
}
defineProperties(WebMercatorProjection.prototype, {
/**
* Gets the {@link Ellipsoid}.
*
* @memberof WebMercatorProjection.prototype
*
* @type {Ellipsoid}
* @readonly
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
}
});
/**
* Converts a Mercator angle, in the range -PI to PI, to a geodetic latitude
* in the range -PI/2 to PI/2.
*
* @param {Number} mercatorAngle The angle to convert.
* @returns {Number} The geodetic latitude in radians.
*/
WebMercatorProjection.mercatorAngleToGeodeticLatitude = function(mercatorAngle) {
return CesiumMath.PI_OVER_TWO - (2.0 * Math.atan(Math.exp(-mercatorAngle)));
};
/**
* Converts a geodetic latitude in radians, in the range -PI/2 to PI/2, to a Mercator
* angle in the range -PI to PI.
*
* @param {Number} latitude The geodetic latitude in radians.
* @returns {Number} The Mercator angle.
*/
WebMercatorProjection.geodeticLatitudeToMercatorAngle = function(latitude) {
// Clamp the latitude coordinate to the valid Mercator bounds.
if (latitude > WebMercatorProjection.MaximumLatitude) {
latitude = WebMercatorProjection.MaximumLatitude;
} else if (latitude < -WebMercatorProjection.MaximumLatitude) {
latitude = -WebMercatorProjection.MaximumLatitude;
}
var sinLatitude = Math.sin(latitude);
return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
};
/**
* The maximum latitude (both North and South) supported by a Web Mercator
* (EPSG:3857) projection. Technically, the Mercator projection is defined
* for any latitude up to (but not including) 90 degrees, but it makes sense
* to cut it off sooner because it grows exponentially with increasing latitude.
* The logic behind this particular cutoff value, which is the one used by
* Google Maps, Bing Maps, and Esri, is that it makes the projection
* square. That is, the rectangle is equal in the X and Y directions.
*
* The constant value is computed by calling:
* WebMercatorProjection.mercatorAngleToGeodeticLatitude(Math.PI)
*
* @type {Number}
*/
WebMercatorProjection.MaximumLatitude = WebMercatorProjection.mercatorAngleToGeodeticLatitude(Math.PI);
/**
* Converts geodetic ellipsoid coordinates, in radians, to the equivalent Web Mercator
* X, Y, Z coordinates expressed in meters and returned in a {@link Cartesian3}. The height
* is copied unmodified to the Z coordinate.
*
* @param {Cartographic} cartographic The cartographic coordinates in radians.
* @param {Cartesian3} [result] The instance to which to copy the result, or undefined if a
* new instance should be created.
* @returns {Cartesian3} The equivalent web mercator X, Y, Z coordinates, in meters.
*/
WebMercatorProjection.prototype.project = function(cartographic, result) {
var semimajorAxis = this._semimajorAxis;
var x = cartographic.longitude * semimajorAxis;
var y = WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographic.latitude) * semimajorAxis;
var z = cartographic.height;
if (!defined(result)) {
return new Cartesian3(x, y, z);
}
result.x = x;
result.y = y;
result.z = z;
return result;
};
/**
* Converts Web Mercator X, Y coordinates, expressed in meters, to a {@link Cartographic}
* containing geodetic ellipsoid coordinates. The Z coordinate is copied unmodified to the
* height.
*
* @param {Cartesian3} cartesian The web mercator Cartesian position to unrproject with height (z) in meters.
* @param {Cartographic} [result] The instance to which to copy the result, or undefined if a
* new instance should be created.
* @returns {Cartographic} The equivalent cartographic coordinates.
*/
WebMercatorProjection.prototype.unproject = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
var oneOverEarthSemimajorAxis = this._oneOverSemimajorAxis;
var longitude = cartesian.x * oneOverEarthSemimajorAxis;
var latitude = WebMercatorProjection.mercatorAngleToGeodeticLatitude(cartesian.y * oneOverEarthSemimajorAxis);
var height = cartesian.z;
if (!defined(result)) {
return new Cartographic(longitude, latitude, height);
}
result.longitude = longitude;
result.latitude = latitude;
result.height = height;
return result;
};
return WebMercatorProjection;
});
/*global define*/
define('Core/HeightmapTessellator',[
'./AxisAlignedBoundingBox',
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./EllipsoidalOccluder',
'./freezeObject',
'./Math',
'./Matrix4',
'./OrientedBoundingBox',
'./Rectangle',
'./TerrainEncoding',
'./Transforms',
'./WebMercatorProjection'
], function(
AxisAlignedBoundingBox,
BoundingSphere,
Cartesian2,
Cartesian3,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
EllipsoidalOccluder,
freezeObject,
CesiumMath,
Matrix4,
OrientedBoundingBox,
Rectangle,
TerrainEncoding,
Transforms,
WebMercatorProjection) {
'use strict';
/**
* Contains functions to create a mesh from a heightmap image.
*
* @exports HeightmapTessellator
*
* @private
*/
var HeightmapTessellator = {};
/**
* The default structure of a heightmap, as given to {@link HeightmapTessellator.computeVertices}.
*
* @constant
*/
HeightmapTessellator.DEFAULT_STRUCTURE = freezeObject({
heightScale : 1.0,
heightOffset : 0.0,
elementsPerHeight : 1,
stride : 1,
elementMultiplier : 256.0,
isBigEndian : false
});
var cartesian3Scratch = new Cartesian3();
var matrix4Scratch = new Matrix4();
var minimumScratch = new Cartesian3();
var maximumScratch = new Cartesian3();
/**
* Fills an array of vertices from a heightmap image.
*
* @param {Object} options Object with the following properties:
* @param {TypedArray} options.heightmap The heightmap to tessellate.
* @param {Number} options.width The width of the heightmap, in height samples.
* @param {Number} options.height The height of the heightmap, in height samples.
* @param {Number} options.skirtHeight The height of skirts to drape at the edges of the heightmap.
* @param {Rectangle} options.nativeRectangle An rectangle in the native coordinates of the heightmap's projection. For
* a heightmap with a geographic projection, this is degrees. For the web mercator
* projection, this is meters.
* @param {Number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
* @param {Rectangle} [options.rectangle] The rectangle covered by the heightmap, in geodetic coordinates with north, south, east and
* west properties in radians. Either rectangle or nativeRectangle must be provided. If both
* are provided, they're assumed to be consistent.
* @param {Boolean} [options.isGeographic=true] True if the heightmap uses a {@link GeographicProjection}, or false if it uses
* a {@link WebMercatorProjection}.
* @param {Cartesian3} [options.relativeToCenter=Cartesian3.ZERO] The positions will be computed as Cartesian3.subtract(worldPosition, relativeToCenter)
.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to which the heightmap applies.
* @param {Object} [options.structure] An object describing the structure of the height data.
* @param {Number} [options.structure.heightScale=1.0] The factor by which to multiply height samples in order to obtain
* the height above the heightOffset, in meters. The heightOffset is added to the resulting
* height after multiplying by the scale.
* @param {Number} [options.structure.heightOffset=0.0] The offset to add to the scaled height to obtain the final
* height in meters. The offset is added after the height sample is multiplied by the
* heightScale.
* @param {Number} [options.structure.elementsPerHeight=1] The number of elements in the buffer that make up a single height
* sample. This is usually 1, indicating that each element is a separate height sample. If
* it is greater than 1, that number of elements together form the height sample, which is
* computed according to the structure.elementMultiplier and structure.isBigEndian properties.
* @param {Number} [options.structure.stride=1] The number of elements to skip to get from the first element of
* one height to the first element of the next height.
* @param {Number} [options.structure.elementMultiplier=256.0] The multiplier used to compute the height value when the
* stride property is greater than 1. For example, if the stride is 4 and the strideMultiplier
* is 256, the height is computed as follows:
* `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256`
* This is assuming that the isBigEndian property is false. If it is true, the order of the
* elements is reversed.
* @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer. Any heights that are lower
* than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
* buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers. If this parameter is
* not specified, no minimum value is enforced.
* @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer. Any heights that are higher
* than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
* buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger
* than 65535. If this parameter is not specified, no maximum value is enforced.
* @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the
* stride property is greater than 1. If this property is false, the first element is the
* low-order element. If it is true, the first element is the high-order element.
*
* @example
* var width = 5;
* var height = 5;
* var statistics = Cesium.HeightmapTessellator.computeVertices({
* heightmap : [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
* width : width,
* height : height,
* skirtHeight : 0.0,
* nativeRectangle : {
* west : 10.0,
* east : 20.0,
* south : 30.0,
* north : 40.0
* }
* });
*
* var encoding = statistics.encoding;
* var position = encoding.decodePosition(statistics.vertices, index * encoding.getStride());
*/
HeightmapTessellator.computeVertices = function(options) {
if (!defined(options) || !defined(options.heightmap)) {
throw new DeveloperError('options.heightmap is required.');
}
if (!defined(options.width) || !defined(options.height)) {
throw new DeveloperError('options.width and options.height are required.');
}
if (!defined(options.nativeRectangle)) {
throw new DeveloperError('options.nativeRectangle is required.');
}
if (!defined(options.skirtHeight)) {
throw new DeveloperError('options.skirtHeight is required.');
}
// This function tends to be a performance hotspot for terrain rendering,
// so it employs a lot of inlining and unrolling as an optimization.
// In particular, the functionality of Ellipsoid.cartographicToCartesian
// is inlined.
var cos = Math.cos;
var sin = Math.sin;
var sqrt = Math.sqrt;
var atan = Math.atan;
var exp = Math.exp;
var piOverTwo = CesiumMath.PI_OVER_TWO;
var toRadians = CesiumMath.toRadians;
var heightmap = options.heightmap;
var width = options.width;
var height = options.height;
var skirtHeight = options.skirtHeight;
var isGeographic = defaultValue(options.isGeographic, true);
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var oneOverGlobeSemimajorAxis = 1.0 / ellipsoid.maximumRadius;
var nativeRectangle = options.nativeRectangle;
var geographicWest;
var geographicSouth;
var geographicEast;
var geographicNorth;
var rectangle = options.rectangle;
if (!defined(rectangle)) {
if (isGeographic) {
geographicWest = toRadians(nativeRectangle.west);
geographicSouth = toRadians(nativeRectangle.south);
geographicEast = toRadians(nativeRectangle.east);
geographicNorth = toRadians(nativeRectangle.north);
} else {
geographicWest = nativeRectangle.west * oneOverGlobeSemimajorAxis;
geographicSouth = piOverTwo - (2.0 * atan(exp(-nativeRectangle.south * oneOverGlobeSemimajorAxis)));
geographicEast = nativeRectangle.east * oneOverGlobeSemimajorAxis;
geographicNorth = piOverTwo - (2.0 * atan(exp(-nativeRectangle.north * oneOverGlobeSemimajorAxis)));
}
} else {
geographicWest = rectangle.west;
geographicSouth = rectangle.south;
geographicEast = rectangle.east;
geographicNorth = rectangle.north;
}
var relativeToCenter = options.relativeToCenter;
var hasRelativeToCenter = defined(relativeToCenter);
relativeToCenter = hasRelativeToCenter ? relativeToCenter : Cartesian3.ZERO;
var exaggeration = defaultValue(options.exaggeration, 1.0);
var includeWebMercatorT = defaultValue(options.includeWebMercatorT, false);
var structure = defaultValue(options.structure, HeightmapTessellator.DEFAULT_STRUCTURE);
var heightScale = defaultValue(structure.heightScale, HeightmapTessellator.DEFAULT_STRUCTURE.heightScale);
var heightOffset = defaultValue(structure.heightOffset, HeightmapTessellator.DEFAULT_STRUCTURE.heightOffset);
var elementsPerHeight = defaultValue(structure.elementsPerHeight, HeightmapTessellator.DEFAULT_STRUCTURE.elementsPerHeight);
var stride = defaultValue(structure.stride, HeightmapTessellator.DEFAULT_STRUCTURE.stride);
var elementMultiplier = defaultValue(structure.elementMultiplier, HeightmapTessellator.DEFAULT_STRUCTURE.elementMultiplier);
var isBigEndian = defaultValue(structure.isBigEndian, HeightmapTessellator.DEFAULT_STRUCTURE.isBigEndian);
var granularityX = Rectangle.computeWidth(nativeRectangle) / (width - 1);
var granularityY = Rectangle.computeHeight(nativeRectangle) / (height - 1);
var radiiSquared = ellipsoid.radiiSquared;
var radiiSquaredX = radiiSquared.x;
var radiiSquaredY = radiiSquared.y;
var radiiSquaredZ = radiiSquared.z;
var minimumHeight = 65536.0;
var maximumHeight = -65536.0;
var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid);
var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch);
var southMercatorY;
var oneOverMercatorHeight;
if (includeWebMercatorT) {
southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth);
oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) - southMercatorY);
}
var minimum = minimumScratch;
minimum.x = Number.POSITIVE_INFINITY;
minimum.y = Number.POSITIVE_INFINITY;
minimum.z = Number.POSITIVE_INFINITY;
var maximum = maximumScratch;
maximum.x = Number.NEGATIVE_INFINITY;
maximum.y = Number.NEGATIVE_INFINITY;
maximum.z = Number.NEGATIVE_INFINITY;
var hMin = Number.POSITIVE_INFINITY;
var arrayWidth = width + (skirtHeight > 0.0 ? 2.0 : 0.0);
var arrayHeight = height + (skirtHeight > 0.0 ? 2.0 : 0.0);
var size = arrayWidth * arrayHeight;
var positions = new Array(size);
var heights = new Array(size);
var uvs = new Array(size);
var webMercatorTs = includeWebMercatorT ? new Array(size) : [];
var startRow = 0;
var endRow = height;
var startCol = 0;
var endCol = width;
if (skirtHeight > 0) {
--startRow;
++endRow;
--startCol;
++endCol;
}
var index = 0;
for (var rowIndex = startRow; rowIndex < endRow; ++rowIndex) {
var row = rowIndex;
if (row < 0) {
row = 0;
}
if (row >= height) {
row = height - 1;
}
var latitude = nativeRectangle.north - granularityY * row;
if (!isGeographic) {
latitude = piOverTwo - (2.0 * atan(exp(-latitude * oneOverGlobeSemimajorAxis)));
} else {
latitude = toRadians(latitude);
}
var cosLatitude = cos(latitude);
var nZ = sin(latitude);
var kZ = radiiSquaredZ * nZ;
var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth);
v = CesiumMath.clamp(v, 0.0, 1.0);
var webMercatorT;
if (includeWebMercatorT) {
webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight;
}
for (var colIndex = startCol; colIndex < endCol; ++colIndex) {
var col = colIndex;
if (col < 0) {
col = 0;
}
if (col >= width) {
col = width - 1;
}
var longitude = nativeRectangle.west + granularityX * col;
if (!isGeographic) {
longitude = longitude * oneOverGlobeSemimajorAxis;
} else {
longitude = toRadians(longitude);
}
var terrainOffset = row * (width * stride) + col * stride;
var heightSample;
if (elementsPerHeight === 1) {
heightSample = heightmap[terrainOffset];
} else {
heightSample = 0;
var elementOffset;
if (isBigEndian) {
for (elementOffset = 0; elementOffset < elementsPerHeight; ++elementOffset) {
heightSample = (heightSample * elementMultiplier) + heightmap[terrainOffset + elementOffset];
}
} else {
for (elementOffset = elementsPerHeight - 1; elementOffset >= 0; --elementOffset) {
heightSample = (heightSample * elementMultiplier) + heightmap[terrainOffset + elementOffset];
}
}
}
heightSample = (heightSample * heightScale + heightOffset) * exaggeration;
maximumHeight = Math.max(maximumHeight, heightSample);
minimumHeight = Math.min(minimumHeight, heightSample);
if (colIndex !== col || rowIndex !== row) {
heightSample -= skirtHeight;
}
var nX = cosLatitude * cos(longitude);
var nY = cosLatitude * sin(longitude);
var kX = radiiSquaredX * nX;
var kY = radiiSquaredY * nY;
var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ));
var oneOverGamma = 1.0 / gamma;
var rSurfaceX = kX * oneOverGamma;
var rSurfaceY = kY * oneOverGamma;
var rSurfaceZ = kZ * oneOverGamma;
var position = new Cartesian3();
position.x = rSurfaceX + nX * heightSample;
position.y = rSurfaceY + nY * heightSample;
position.z = rSurfaceZ + nZ * heightSample;
positions[index] = position;
heights[index] = heightSample;
var u = (longitude - geographicWest) / (geographicEast - geographicWest);
u = CesiumMath.clamp(u, 0.0, 1.0);
uvs[index] = new Cartesian2(u, v);
if (includeWebMercatorT) {
webMercatorTs[index] = webMercatorT;
}
index++;
Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch);
Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum);
Cartesian3.maximumByComponent(cartesian3Scratch, maximum, maximum);
hMin = Math.min(hMin, heightSample);
}
}
var boundingSphere3D = BoundingSphere.fromPoints(positions);
var orientedBoundingBox;
if (defined(rectangle) && rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) {
// Here, rectangle.width < pi/2, and rectangle.height < pi
// (though it would still work with rectangle.width up to pi)
orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid);
}
var occludeePointInScaledSpace;
if (hasRelativeToCenter) {
var occluder = new EllipsoidalOccluder(ellipsoid);
occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions);
}
var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, false, includeWebMercatorT);
var vertices = new Float32Array(size * encoding.getStride());
var bufferIndex = 0;
for (var j = 0; j < size; ++j) {
bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j], undefined, webMercatorTs[j]);
}
return {
vertices : vertices,
maximumHeight : maximumHeight,
minimumHeight : minimumHeight,
encoding : encoding,
boundingSphere3D : boundingSphere3D,
orientedBoundingBox : orientedBoundingBox,
occludeePointInScaledSpace : occludeePointInScaledSpace
};
};
return HeightmapTessellator;
});
/*global define*/
define('Core/destroyObject',[
'./defaultValue',
'./DeveloperError'
], function(
defaultValue,
DeveloperError) {
'use strict';
function returnTrue() {
return true;
}
/**
* Destroys an object. Each of the object's functions, including functions in its prototype,
* is replaced with a function that throws a {@link DeveloperError}, except for the object's
* isDestroyed
function, which is set to a function that returns true
.
* The object's properties are removed with delete
.
*
* This function is used by objects that hold native resources, e.g., WebGL resources, which
* need to be explicitly released. Client code calls an object's destroy
function,
* which then releases the native resource and calls destroyObject
to put itself
* in a destroyed state.
*
* @exports destroyObject
*
* @param {Object} object The object to destroy.
* @param {String} [message] The message to include in the exception that is thrown if
* a destroyed object's function is called.
*
*
* @example
* // How a texture would destroy itself.
* this.destroy = function () {
* _gl.deleteTexture(_texture);
* return Cesium.destroyObject(this);
* };
*
* @see DeveloperError
*/
function destroyObject(object, message) {
message = defaultValue(message, 'This object was destroyed, i.e., destroy() was called.');
function throwOnDestroyed() {
throw new DeveloperError(message);
}
for ( var key in object) {
if (typeof object[key] === 'function') {
object[key] = throwOnDestroyed;
}
}
object.isDestroyed = returnTrue;
return undefined;
}
return destroyObject;
});
/*global define*/
define('Core/isCrossOriginUrl',[
'./defined'
], function(
defined) {
'use strict';
var a;
/**
* Given a URL, determine whether that URL is considered cross-origin to the current page.
*
* @private
*/
function isCrossOriginUrl(url) {
if (!defined(a)) {
a = document.createElement('a');
}
// copy window location into the anchor to get consistent results
// when the port is default for the protocol (e.g. 80 for HTTP)
a.href = window.location.href;
// host includes both hostname and port if the port is not standard
var host = a.host;
var protocol = a.protocol;
a.href = url;
a.href = a.href; // IE only absolutizes href on get, not set
return protocol !== a.protocol || host !== a.host;
}
return isCrossOriginUrl;
});
/*global define*/
define('Core/TaskProcessor',[
'../ThirdParty/when',
'./buildModuleUrl',
'./defaultValue',
'./defined',
'./destroyObject',
'./DeveloperError',
'./getAbsoluteUri',
'./isCrossOriginUrl',
'./RuntimeError',
'require'
], function(
when,
buildModuleUrl,
defaultValue,
defined,
destroyObject,
DeveloperError,
getAbsoluteUri,
isCrossOriginUrl,
RuntimeError,
require) {
'use strict';
function canTransferArrayBuffer() {
if (!defined(TaskProcessor._canTransferArrayBuffer)) {
var worker = new Worker(getWorkerUrl('Workers/transferTypedArrayTest.js'));
worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);
var value = 99;
var array = new Int8Array([value]);
try {
// postMessage might fail with a DataCloneError
// if transferring array buffers is not supported.
worker.postMessage({
array : array
}, [array.buffer]);
} catch (e) {
TaskProcessor._canTransferArrayBuffer = false;
return TaskProcessor._canTransferArrayBuffer;
}
var deferred = when.defer();
worker.onmessage = function(event) {
var array = event.data.array;
// some versions of Firefox silently fail to transfer typed arrays.
// https://bugzilla.mozilla.org/show_bug.cgi?id=841904
// Check to make sure the value round-trips successfully.
var result = defined(array) && array[0] === value;
deferred.resolve(result);
worker.terminate();
TaskProcessor._canTransferArrayBuffer = result;
};
TaskProcessor._canTransferArrayBuffer = deferred.promise;
}
return TaskProcessor._canTransferArrayBuffer;
}
function completeTask(processor, data) {
--processor._activeTasks;
var id = data.id;
if (!defined(id)) {
// This is not one of ours.
return;
}
var deferreds = processor._deferreds;
var deferred = deferreds[id];
if (defined(data.error)) {
var error = data.error;
if (error.name === 'RuntimeError') {
error = new RuntimeError(data.error.message);
error.stack = data.error.stack;
} else if (error.name === 'DeveloperError') {
error = new DeveloperError(data.error.message);
error.stack = data.error.stack;
}
deferred.reject(error);
} else {
deferred.resolve(data.result);
}
delete deferreds[id];
}
function getWorkerUrl(moduleID) {
var url = buildModuleUrl(moduleID);
if (isCrossOriginUrl(url)) {
//to load cross-origin, create a shim worker from a blob URL
var script = 'importScripts("' + url + '");';
var blob;
try {
blob = new Blob([script], {
type : 'application/javascript'
});
} catch (e) {
var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
var blobBuilder = new BlobBuilder();
blobBuilder.append(script);
blob = blobBuilder.getBlob('application/javascript');
}
var URL = window.URL || window.webkitURL;
url = URL.createObjectURL(blob);
}
return url;
}
var bootstrapperUrlResult;
function getBootstrapperUrl() {
if (!defined(bootstrapperUrlResult)) {
bootstrapperUrlResult = getWorkerUrl('Workers/cesiumWorkerBootstrapper.js');
}
return bootstrapperUrlResult;
}
function createWorker(processor) {
var worker = new Worker(getBootstrapperUrl());
worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);
var bootstrapMessage = {
loaderConfig : {},
workerModule : TaskProcessor._workerModulePrefix + processor._workerName
};
if (defined(TaskProcessor._loaderConfig)) {
bootstrapMessage.loaderConfig = TaskProcessor._loaderConfig;
} else if (defined(require.toUrl)) {
bootstrapMessage.loaderConfig.baseUrl =
getAbsoluteUri('..', buildModuleUrl('Workers/cesiumWorkerBootstrapper.js'));
} else {
bootstrapMessage.loaderConfig.paths = {
'Workers' : buildModuleUrl('Workers')
};
}
worker.postMessage(bootstrapMessage);
worker.onmessage = function(event) {
completeTask(processor, event.data);
};
return worker;
}
/**
* A wrapper around a web worker that allows scheduling tasks for a given worker,
* returning results asynchronously via a promise.
*
* The Worker is not constructed until a task is scheduled.
*
* @alias TaskProcessor
* @constructor
*
* @param {String} workerName The name of the worker. This is expected to be a script
* in the Workers folder.
* @param {Number} [maximumActiveTasks=5] The maximum number of active tasks. Once exceeded,
* scheduleTask will not queue any more tasks, allowing
* work to be rescheduled in future frames.
*/
function TaskProcessor(workerName, maximumActiveTasks) {
this._workerName = workerName;
this._maximumActiveTasks = defaultValue(maximumActiveTasks, 5);
this._activeTasks = 0;
this._deferreds = {};
this._nextID = 0;
}
var emptyTransferableObjectArray = [];
/**
* Schedule a task to be processed by the web worker asynchronously. If there are currently more
* tasks active than the maximum set by the constructor, will immediately return undefined.
* Otherwise, returns a promise that will resolve to the result posted back by the worker when
* finished.
*
* @param {*} parameters Any input data that will be posted to the worker.
* @param {Object[]} [transferableObjects] An array of objects contained in parameters that should be
* transferred to the worker instead of copied.
* @returns {Promise.|undefined} Either a promise that will resolve to the result when available, or undefined
* if there are too many active tasks,
*
* @example
* var taskProcessor = new Cesium.TaskProcessor('myWorkerName');
* var promise = taskProcessor.scheduleTask({
* someParameter : true,
* another : 'hello'
* });
* if (!Cesium.defined(promise)) {
* // too many active tasks - try again later
* } else {
* Cesium.when(promise, function(result) {
* // use the result of the task
* });
* }
*/
TaskProcessor.prototype.scheduleTask = function(parameters, transferableObjects) {
if (!defined(this._worker)) {
this._worker = createWorker(this);
}
if (this._activeTasks >= this._maximumActiveTasks) {
return undefined;
}
++this._activeTasks;
var processor = this;
return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) {
if (!defined(transferableObjects)) {
transferableObjects = emptyTransferableObjectArray;
} else if (!canTransferArrayBuffer) {
transferableObjects.length = 0;
}
var id = processor._nextID++;
var deferred = when.defer();
processor._deferreds[id] = deferred;
processor._worker.postMessage({
id : id,
parameters : parameters,
canTransferArrayBuffer : canTransferArrayBuffer
}, transferableObjects);
return deferred.promise;
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see TaskProcessor#destroy
*/
TaskProcessor.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys this object. This will immediately terminate the Worker.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {undefined}
*/
TaskProcessor.prototype.destroy = function() {
if (defined(this._worker)) {
this._worker.terminate();
}
return destroyObject(this);
};
// exposed for testing purposes
TaskProcessor._defaultWorkerModulePrefix = 'Workers/';
TaskProcessor._workerModulePrefix = TaskProcessor._defaultWorkerModulePrefix;
TaskProcessor._loaderConfig = undefined;
TaskProcessor._canTransferArrayBuffer = undefined;
return TaskProcessor;
});
/*global define*/
define('Core/TerrainMesh',[
'./defaultValue'
], function(
defaultValue) {
'use strict';
/**
* A mesh plus related metadata for a single tile of terrain. Instances of this type are
* usually created from raw {@link TerrainData}.
*
* @alias TerrainMesh
* @constructor
*
* @param {Cartesian3} center The center of the tile. Vertex positions are specified relative to this center.
* @param {Float32Array} vertices The vertex data, including positions, texture coordinates, and heights.
* The vertex data is in the order [X, Y, Z, H, U, V], where X, Y, and Z represent
* the Cartesian position of the vertex, H is the height above the ellipsoid, and
* U and V are the texture coordinates.
* @param {Uint16Array|Uint32Array} indices The indices describing how the vertices are connected to form triangles.
* @param {Number} minimumHeight The lowest height in the tile, in meters above the ellipsoid.
* @param {Number} maximumHeight The highest height in the tile, in meters above the ellipsoid.
* @param {BoundingSphere} boundingSphere3D A bounding sphere that completely contains the tile.
* @param {Cartesian3} occludeePointInScaledSpace The occludee point of the tile, represented in ellipsoid-
* scaled space, and used for horizon culling. If this point is below the horizon,
* the tile is considered to be entirely below the horizon.
* @param {Number} [vertexStride=6] The number of components in each vertex.
* @param {OrientedBoundingBox} [orientedBoundingBox] A bounding box that completely contains the tile.
* @param {TerrainEncoding} encoding Information used to decode the mesh.
*
* @private
*/
function TerrainMesh(center, vertices, indices, minimumHeight, maximumHeight, boundingSphere3D, occludeePointInScaledSpace, vertexStride, orientedBoundingBox, encoding, exaggeration) {
/**
* The center of the tile. Vertex positions are specified relative to this center.
* @type {Cartesian3}
*/
this.center = center;
/**
* The vertex data, including positions, texture coordinates, and heights.
* The vertex data is in the order [X, Y, Z, H, U, V], where X, Y, and Z represent
* the Cartesian position of the vertex, H is the height above the ellipsoid, and
* U and V are the texture coordinates. The vertex data may have additional attributes after those
* mentioned above when the {@link TerrainMesh#stride} is greater than 6.
* @type {Float32Array}
*/
this.vertices = vertices;
/**
* The number of components in each vertex. Typically this is 6 for the 6 components
* [X, Y, Z, H, U, V], but if each vertex has additional data (such as a vertex normal), this value
* may be higher.
* @type {Number}
*/
this.stride = defaultValue(vertexStride, 6);
/**
* The indices describing how the vertices are connected to form triangles.
* @type {Uint16Array|Uint32Array}
*/
this.indices = indices;
/**
* The lowest height in the tile, in meters above the ellipsoid.
* @type {Number}
*/
this.minimumHeight = minimumHeight;
/**
* The highest height in the tile, in meters above the ellipsoid.
* @type {Number}
*/
this.maximumHeight = maximumHeight;
/**
* A bounding sphere that completely contains the tile.
* @type {BoundingSphere}
*/
this.boundingSphere3D = boundingSphere3D;
/**
* The occludee point of the tile, represented in ellipsoid-
* scaled space, and used for horizon culling. If this point is below the horizon,
* the tile is considered to be entirely below the horizon.
* @type {Cartesian3}
*/
this.occludeePointInScaledSpace = occludeePointInScaledSpace;
/**
* A bounding box that completely contains the tile.
* @type {OrientedBoundingBox}
*/
this.orientedBoundingBox = orientedBoundingBox;
/**
* Information for decoding the mesh vertices.
* @type {TerrainEncoding}
*/
this.encoding = encoding;
/**
* The amount that this mesh was exaggerated.
* @type {Number}
*/
this.exaggeration = exaggeration;
}
return TerrainMesh;
});
/*global define*/
define('Core/TerrainProvider',[
'./defined',
'./defineProperties',
'./DeveloperError',
'./Math'
], function(
defined,
defineProperties,
DeveloperError,
CesiumMath) {
'use strict';
/**
* Provides terrain or other geometry for the surface of an ellipsoid. The surface geometry is
* organized into a pyramid of tiles according to a {@link TilingScheme}. This type describes an
* interface and is not intended to be instantiated directly.
*
* @alias TerrainProvider
* @constructor
*
* @see EllipsoidTerrainProvider
* @see CesiumTerrainProvider
* @see ArcGisImageServerTerrainProvider
*/
function TerrainProvider() {
DeveloperError.throwInstantiationError();
}
defineProperties(TerrainProvider.prototype, {
/**
* Gets an event that is raised when the terrain provider encounters an asynchronous error.. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof TerrainProvider.prototype
* @type {Event}
*/
errorEvent : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the credit to display when this terrain provider is active. Typically this is used to credit
* the source of the terrain. This function should
* not be called before {@link TerrainProvider#ready} returns true.
* @memberof TerrainProvider.prototype
* @type {Credit}
*/
credit : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the tiling scheme used by the provider. This function should
* not be called before {@link TerrainProvider#ready} returns true.
* @memberof TerrainProvider.prototype
* @type {TilingScheme}
*/
tilingScheme : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof TerrainProvider.prototype
* @type {Boolean}
*/
ready : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof TerrainProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating whether or not the provider includes a water mask. The water mask
* indicates which areas of the globe are water rather than land, so they can be rendered
* as a reflective surface with animated waves. This function should not be
* called before {@link TerrainProvider#ready} returns true.
* @memberof TerrainProvider.prototype
* @type {Boolean}
*/
hasWaterMask : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating whether or not the requested tiles include vertex normals.
* This function should not be called before {@link TerrainProvider#ready} returns true.
* @memberof TerrainProvider.prototype
* @type {Boolean}
*/
hasVertexNormals : {
get : DeveloperError.throwInstantiationError
}
});
var regularGridIndexArrays = [];
/**
* Gets a list of indices for a triangle mesh representing a regular grid. Calling
* this function multiple times with the same grid width and height returns the
* same list of indices. The total number of vertices must be less than or equal
* to 65536.
*
* @param {Number} width The number of vertices in the regular grid in the horizontal direction.
* @param {Number} height The number of vertices in the regular grid in the vertical direction.
* @returns {Uint16Array} The list of indices.
*/
TerrainProvider.getRegularGridIndices = function(width, height) {
if (width * height >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
throw new DeveloperError('The total number of vertices (width * height) must be less than 65536.');
}
var byWidth = regularGridIndexArrays[width];
if (!defined(byWidth)) {
regularGridIndexArrays[width] = byWidth = [];
}
var indices = byWidth[height];
if (!defined(indices)) {
indices = byWidth[height] = new Uint16Array((width - 1) * (height - 1) * 6);
var index = 0;
var indicesIndex = 0;
for (var j = 0; j < height - 1; ++j) {
for (var i = 0; i < width - 1; ++i) {
var upperLeft = index;
var lowerLeft = upperLeft + width;
var lowerRight = lowerLeft + 1;
var upperRight = upperLeft + 1;
indices[indicesIndex++] = upperLeft;
indices[indicesIndex++] = lowerLeft;
indices[indicesIndex++] = upperRight;
indices[indicesIndex++] = upperRight;
indices[indicesIndex++] = lowerLeft;
indices[indicesIndex++] = lowerRight;
++index;
}
++index;
}
}
return indices;
};
/**
* Specifies the quality of terrain created from heightmaps. A value of 1.0 will
* ensure that adjacent heightmap vertices are separated by no more than
* {@link Globe.maximumScreenSpaceError} screen pixels and will probably go very slowly.
* A value of 0.5 will cut the estimated level zero geometric error in half, allowing twice the
* screen pixels between adjacent heightmap vertices and thus rendering more quickly.
* @type {Number}
*/
TerrainProvider.heightmapTerrainQuality = 0.25;
/**
* Determines an appropriate geometric error estimate when the geometry comes from a heightmap.
*
* @param {Ellipsoid} ellipsoid The ellipsoid to which the terrain is attached.
* @param {Number} tileImageWidth The width, in pixels, of the heightmap associated with a single tile.
* @param {Number} numberOfTilesAtLevelZero The number of tiles in the horizontal direction at tile level zero.
* @returns {Number} An estimated geometric error.
*/
TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap = function(ellipsoid, tileImageWidth, numberOfTilesAtLevelZero) {
return ellipsoid.maximumRadius * 2 * Math.PI * TerrainProvider.heightmapTerrainQuality / (tileImageWidth * numberOfTilesAtLevelZero);
};
/**
* Requests the geometry for a given tile. This function should not be called before
* {@link TerrainProvider#ready} returns true. The result must include terrain data and
* may optionally include a water mask and an indication of which child tiles are available.
* @function
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
* or false if the request should be initiated regardless of the number of requests
* already in progress.
* @returns {Promise.|undefined} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
TerrainProvider.prototype.requestTileGeometry = DeveloperError.throwInstantiationError;
/**
* Gets the maximum geometric error allowed in a tile at a given level. This function should not be
* called before {@link TerrainProvider#ready} returns true.
* @function
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error.
*/
TerrainProvider.prototype.getLevelMaximumGeometricError = DeveloperError.throwInstantiationError;
/**
* Determines whether data for a tile is available to be loaded.
* @function
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @returns {Boolean} Undefined if not supported by the terrain provider, otherwise true or false.
*/
TerrainProvider.prototype.getTileDataAvailable = DeveloperError.throwInstantiationError;
return TerrainProvider;
});
/*global define*/
define('Core/HeightmapTerrainData',[
'../ThirdParty/when',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./GeographicTilingScheme',
'./HeightmapTessellator',
'./Math',
'./Rectangle',
'./TaskProcessor',
'./TerrainEncoding',
'./TerrainMesh',
'./TerrainProvider'
], function(
when,
defaultValue,
defined,
defineProperties,
DeveloperError,
GeographicTilingScheme,
HeightmapTessellator,
CesiumMath,
Rectangle,
TaskProcessor,
TerrainEncoding,
TerrainMesh,
TerrainProvider) {
'use strict';
/**
* Terrain data for a single tile where the terrain data is represented as a heightmap. A heightmap
* is a rectangular array of heights in row-major order from south to north and west to east.
*
* @alias HeightmapTerrainData
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {TypedArray} options.buffer The buffer containing height data.
* @param {Number} options.width The width (longitude direction) of the heightmap, in samples.
* @param {Number} options.height The height (latitude direction) of the heightmap, in samples.
* @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
* If a child's bit is set, geometry will be requested for that tile as well when it
* is needed. If the bit is cleared, the child tile is not requested and geometry is
* instead upsampled from the parent. The bit values are as follows:
*
* Bit Position Bit Value Child Tile
* 0 1 Southwest
* 1 2 Southeast
* 2 4 Northwest
* 3 8 Northeast
*
* @param {Object} [options.structure] An object describing the structure of the height data.
* @param {Number} [options.structure.heightScale=1.0] The factor by which to multiply height samples in order to obtain
* the height above the heightOffset, in meters. The heightOffset is added to the resulting
* height after multiplying by the scale.
* @param {Number} [options.structure.heightOffset=0.0] The offset to add to the scaled height to obtain the final
* height in meters. The offset is added after the height sample is multiplied by the
* heightScale.
* @param {Number} [options.structure.elementsPerHeight=1] The number of elements in the buffer that make up a single height
* sample. This is usually 1, indicating that each element is a separate height sample. If
* it is greater than 1, that number of elements together form the height sample, which is
* computed according to the structure.elementMultiplier and structure.isBigEndian properties.
* @param {Number} [options.structure.stride=1] The number of elements to skip to get from the first element of
* one height to the first element of the next height.
* @param {Number} [options.structure.elementMultiplier=256.0] The multiplier used to compute the height value when the
* stride property is greater than 1. For example, if the stride is 4 and the strideMultiplier
* is 256, the height is computed as follows:
* `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256`
* This is assuming that the isBigEndian property is false. If it is true, the order of the
* elements is reversed.
* @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the
* stride property is greater than 1. If this property is false, the first element is the
* low-order element. If it is true, the first element is the high-order element.
* @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer. Any heights that are lower
* than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
* buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers. If this parameter is
* not specified, no minimum value is enforced.
* @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer. Any heights that are higher
* than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value. For example, if the height
* buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger
* than 65535. If this parameter is not specified, no maximum value is enforced.
* @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
* otherwise, false.
*
*
* @example
* var buffer = ...
* var heightBuffer = new Uint16Array(buffer, 0, that._heightmapWidth * that._heightmapWidth);
* var childTileMask = new Uint8Array(buffer, heightBuffer.byteLength, 1)[0];
* var waterMask = new Uint8Array(buffer, heightBuffer.byteLength + 1, buffer.byteLength - heightBuffer.byteLength - 1);
* var terrainData = new Cesium.HeightmapTerrainData({
* buffer : heightBuffer,
* width : 65,
* height : 65,
* childTileMask : childTileMask,
* waterMask : waterMask
* });
*
* @see TerrainData
* @see QuantizedMeshTerrainData
*/
function HeightmapTerrainData(options) {
if (!defined(options) || !defined(options.buffer)) {
throw new DeveloperError('options.buffer is required.');
}
if (!defined(options.width)) {
throw new DeveloperError('options.width is required.');
}
if (!defined(options.height)) {
throw new DeveloperError('options.height is required.');
}
this._buffer = options.buffer;
this._width = options.width;
this._height = options.height;
this._childTileMask = defaultValue(options.childTileMask, 15);
var defaultStructure = HeightmapTessellator.DEFAULT_STRUCTURE;
var structure = options.structure;
if (!defined(structure)) {
structure = defaultStructure;
} else if (structure !== defaultStructure) {
structure.heightScale = defaultValue(structure.heightScale, defaultStructure.heightScale);
structure.heightOffset = defaultValue(structure.heightOffset, defaultStructure.heightOffset);
structure.elementsPerHeight = defaultValue(structure.elementsPerHeight, defaultStructure.elementsPerHeight);
structure.stride = defaultValue(structure.stride, defaultStructure.stride);
structure.elementMultiplier = defaultValue(structure.elementMultiplier, defaultStructure.elementMultiplier);
structure.isBigEndian = defaultValue(structure.isBigEndian, defaultStructure.isBigEndian);
}
this._structure = structure;
this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
this._waterMask = options.waterMask;
this._skirtHeight = undefined;
this._bufferType = this._buffer.constructor;
this._mesh = undefined;
}
defineProperties(HeightmapTerrainData.prototype, {
/**
* The water mask included in this terrain data, if any. A water mask is a rectangular
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof HeightmapTerrainData.prototype
* @type {Uint8Array|Image|Canvas}
*/
waterMask : {
get : function() {
return this._waterMask;
}
}
});
var taskProcessor = new TaskProcessor('createVerticesFromHeightmap');
/**
* Creates a {@link TerrainMesh} from this terrain data.
*
* @private
*
* @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
* @param {Number} x The X coordinate of the tile for which to create the terrain data.
* @param {Number} y The Y coordinate of the tile for which to create the terrain data.
* @param {Number} level The level of the tile for which to create the terrain data.
* @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain.
* @returns {Promise.|undefined} A promise for the terrain mesh, or undefined if too many
* asynchronous mesh creations are already in progress and the operation should
* be retried later.
*/
HeightmapTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) {
if (!defined(tilingScheme)) {
throw new DeveloperError('tilingScheme is required.');
}
if (!defined(x)) {
throw new DeveloperError('x is required.');
}
if (!defined(y)) {
throw new DeveloperError('y is required.');
}
if (!defined(level)) {
throw new DeveloperError('level is required.');
}
var ellipsoid = tilingScheme.ellipsoid;
var nativeRectangle = tilingScheme.tileXYToNativeRectangle(x, y, level);
var rectangle = tilingScheme.tileXYToRectangle(x, y, level);
exaggeration = defaultValue(exaggeration, 1.0);
// Compute the center of the tile for RTC rendering.
var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle));
var structure = this._structure;
var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, this._width, tilingScheme.getNumberOfXTilesAtLevel(0));
var thisLevelMaxError = levelZeroMaxError / (1 << level);
this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0);
var verticesPromise = taskProcessor.scheduleTask({
heightmap : this._buffer,
structure : structure,
includeWebMercatorT : true,
width : this._width,
height : this._height,
nativeRectangle : nativeRectangle,
rectangle : rectangle,
relativeToCenter : center,
ellipsoid : ellipsoid,
skirtHeight : this._skirtHeight,
isGeographic : tilingScheme instanceof GeographicTilingScheme,
exaggeration : exaggeration
});
if (!defined(verticesPromise)) {
// Postponed
return undefined;
}
var that = this;
return when(verticesPromise, function(result) {
that._mesh = new TerrainMesh(
center,
new Float32Array(result.vertices),
TerrainProvider.getRegularGridIndices(result.gridWidth, result.gridHeight),
result.minimumHeight,
result.maximumHeight,
result.boundingSphere3D,
result.occludeePointInScaledSpace,
result.numberOfAttributes,
result.orientedBoundingBox,
TerrainEncoding.clone(result.encoding),
exaggeration);
// Free memory received from server after mesh is created.
that._buffer = undefined;
return that._mesh;
});
};
/**
* Computes the terrain height at a specified longitude and latitude.
*
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
* @param {Number} longitude The longitude in radians.
* @param {Number} latitude The latitude in radians.
* @returns {Number} The terrain height at the specified position. If the position
* is outside the rectangle, this method will extrapolate the height, which is likely to be wildly
* incorrect for positions far outside the rectangle.
*/
HeightmapTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) {
var width = this._width;
var height = this._height;
var structure = this._structure;
var stride = structure.stride;
var elementsPerHeight = structure.elementsPerHeight;
var elementMultiplier = structure.elementMultiplier;
var isBigEndian = structure.isBigEndian;
var heightOffset = structure.heightOffset;
var heightScale = structure.heightScale;
var heightSample;
if (defined(this._mesh)) {
var buffer = this._mesh.vertices;
var encoding = this._mesh.encoding;
var skirtHeight = this._skirtHeight;
var exaggeration = this._mesh.exaggeration;
heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, rectangle, width, height, longitude, latitude, exaggeration);
} else {
heightSample = interpolateHeight(this._buffer, elementsPerHeight, elementMultiplier, stride, isBigEndian, rectangle, width, height, longitude, latitude);
heightSample = heightSample * heightScale + heightOffset;
}
return heightSample;
};
/**
* Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the
* height samples in this instance, interpolated if necessary.
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {Number} thisX The X coordinate of this tile in the tiling scheme.
* @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {Number} thisLevel The level of this tile in the tiling scheme.
* @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
* or undefined if too many asynchronous upsample operations are in progress and the request has been
* deferred.
*/
HeightmapTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) {
if (!defined(tilingScheme)) {
throw new DeveloperError('tilingScheme is required.');
}
if (!defined(thisX)) {
throw new DeveloperError('thisX is required.');
}
if (!defined(thisY)) {
throw new DeveloperError('thisY is required.');
}
if (!defined(thisLevel)) {
throw new DeveloperError('thisLevel is required.');
}
if (!defined(descendantX)) {
throw new DeveloperError('descendantX is required.');
}
if (!defined(descendantY)) {
throw new DeveloperError('descendantY is required.');
}
if (!defined(descendantLevel)) {
throw new DeveloperError('descendantLevel is required.');
}
var levelDifference = descendantLevel - thisLevel;
if (levelDifference > 1) {
throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.');
}
var width = this._width;
var height = this._height;
var structure = this._structure;
var skirtHeight = this._skirtHeight;
var stride = structure.stride;
var heights = new this._bufferType(width * height * stride);
var meshData = this._mesh;
if (!defined(meshData)) {
return undefined;
}
var buffer = meshData.vertices;
var encoding = meshData.encoding;
// PERFORMANCE_IDEA: don't recompute these rectangles - the caller already knows them.
var sourceRectangle = tilingScheme.tileXYToRectangle(thisX, thisY, thisLevel);
var destinationRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel);
var heightOffset = structure.heightOffset;
var heightScale = structure.heightScale;
var exaggeration = meshData.exaggeration;
var elementsPerHeight = structure.elementsPerHeight;
var elementMultiplier = structure.elementMultiplier;
var isBigEndian = structure.isBigEndian;
var divisor = Math.pow(elementMultiplier, elementsPerHeight - 1);
for (var j = 0; j < height; ++j) {
var latitude = CesiumMath.lerp(destinationRectangle.north, destinationRectangle.south, j / (height - 1));
for (var i = 0; i < width; ++i) {
var longitude = CesiumMath.lerp(destinationRectangle.west, destinationRectangle.east, i / (width - 1));
var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration);
// Use conditionals here instead of Math.min and Math.max so that an undefined
// lowestEncodedHeight or highestEncodedHeight has no effect.
heightSample = heightSample < structure.lowestEncodedHeight ? structure.lowestEncodedHeight : heightSample;
heightSample = heightSample > structure.highestEncodedHeight ? structure.highestEncodedHeight : heightSample;
setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, j * width + i, heightSample);
}
}
return new HeightmapTerrainData({
buffer : heights,
width : width,
height : height,
childTileMask : 0,
structure : this._structure,
createdByUpsampling : true
});
};
/**
* Determines if a given child tile is available, based on the
* {@link HeightmapTerrainData.childTileMask}. The given child tile coordinates are assumed
* to be one of the four children of this tile. If non-child tile coordinates are
* given, the availability of the southeast child tile is returned.
*
* @param {Number} thisX The tile X coordinate of this (the parent) tile.
* @param {Number} thisY The tile Y coordinate of this (the parent) tile.
* @param {Number} childX The tile X coordinate of the child tile to check for availability.
* @param {Number} childY The tile Y coordinate of the child tile to check for availability.
* @returns {Boolean} True if the child tile is available; otherwise, false.
*/
HeightmapTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) {
if (!defined(thisX)) {
throw new DeveloperError('thisX is required.');
}
if (!defined(thisY)) {
throw new DeveloperError('thisY is required.');
}
if (!defined(childX)) {
throw new DeveloperError('childX is required.');
}
if (!defined(childY)) {
throw new DeveloperError('childY is required.');
}
var bitNumber = 2; // northwest child
if (childX !== thisX * 2) {
++bitNumber; // east child
}
if (childY !== thisY * 2) {
bitNumber -= 2; // south child
}
return (this._childTileMask & (1 << bitNumber)) !== 0;
};
/**
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
* terrain data. If this value is false, the data was obtained from some other source, such
* as by downloading it from a remote server. This method should return true for instances
* returned from a call to {@link HeightmapTerrainData#upsample}.
*
* @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
*/
HeightmapTerrainData.prototype.wasCreatedByUpsampling = function() {
return this._createdByUpsampling;
};
function interpolateHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, sourceRectangle, width, height, longitude, latitude) {
var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south);
var westInteger = fromWest | 0;
var eastInteger = westInteger + 1;
if (eastInteger >= width) {
eastInteger = width - 1;
westInteger = width - 2;
}
var southInteger = fromSouth | 0;
var northInteger = southInteger + 1;
if (northInteger >= height) {
northInteger = height - 1;
southInteger = height - 2;
}
var dx = fromWest - westInteger;
var dy = fromSouth - southInteger;
southInteger = height - 1 - southInteger;
northInteger = height - 1 - northInteger;
var southwestHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, southInteger * width + westInteger);
var southeastHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, southInteger * width + eastInteger);
var northwestHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, northInteger * width + westInteger);
var northeastHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, northInteger * width + eastInteger);
return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight);
}
function interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration) {
// returns a height encoded according to the structure's heightScale and heightOffset.
var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south);
if (skirtHeight > 0) {
fromWest += 1.0;
fromSouth += 1.0;
width += 2;
height += 2;
}
var widthEdge = (skirtHeight > 0) ? width - 1 : width;
var westInteger = fromWest | 0;
var eastInteger = westInteger + 1;
if (eastInteger >= widthEdge) {
eastInteger = width - 1;
westInteger = width - 2;
}
var heightEdge = (skirtHeight > 0) ? height - 1 : height;
var southInteger = fromSouth | 0;
var northInteger = southInteger + 1;
if (northInteger >= heightEdge) {
northInteger = height - 1;
southInteger = height - 2;
}
var dx = fromWest - westInteger;
var dy = fromSouth - southInteger;
southInteger = height - 1 - southInteger;
northInteger = height - 1 - northInteger;
var southwestHeight = (encoding.decodeHeight(buffer, southInteger * width + westInteger) / exaggeration - heightOffset) / heightScale;
var southeastHeight = (encoding.decodeHeight(buffer, southInteger * width + eastInteger) / exaggeration - heightOffset) / heightScale;
var northwestHeight = (encoding.decodeHeight(buffer, northInteger * width + westInteger) / exaggeration - heightOffset) / heightScale;
var northeastHeight = (encoding.decodeHeight(buffer, northInteger * width + eastInteger) / exaggeration - heightOffset) / heightScale;
return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight);
}
function triangleInterpolateHeight(dX, dY, southwestHeight, southeastHeight, northwestHeight, northeastHeight) {
// The HeightmapTessellator bisects the quad from southwest to northeast.
if (dY < dX) {
// Lower right triangle
return southwestHeight + (dX * (southeastHeight - southwestHeight)) + (dY * (northeastHeight - southeastHeight));
}
// Upper left triangle
return southwestHeight + (dX * (northeastHeight - northwestHeight)) + (dY * (northwestHeight - southwestHeight));
}
function getHeight(heights, elementsPerHeight, elementMultiplier, stride, isBigEndian, index) {
index *= stride;
var height = 0;
var i;
if (isBigEndian) {
for (i = 0; i < elementsPerHeight; ++i) {
height = (height * elementMultiplier) + heights[index + i];
}
} else {
for (i = elementsPerHeight - 1; i >= 0; --i) {
height = (height * elementMultiplier) + heights[index + i];
}
}
return height;
}
function setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, index, height) {
index *= stride;
var i;
if (isBigEndian) {
for (i = 0; i < elementsPerHeight - 1; ++i) {
heights[index + i] = (height / divisor) | 0;
height -= heights[index + i] * divisor;
divisor /= elementMultiplier;
}
} else {
for (i = elementsPerHeight - 1; i > 0; --i) {
heights[index + i] = (height / divisor) | 0;
height -= heights[index + i] * divisor;
divisor /= elementMultiplier;
}
}
heights[index + i] = height;
}
return HeightmapTerrainData;
});
/*global define*/
define('Core/loadImage',[
'../ThirdParty/when',
'./defaultValue',
'./defined',
'./DeveloperError',
'./isCrossOriginUrl',
'./TrustedServers'
], function(
when,
defaultValue,
defined,
DeveloperError,
isCrossOriginUrl,
TrustedServers) {
'use strict';
var dataUriRegex = /^data:/;
/**
* Asynchronously loads the given image URL. Returns a promise that will resolve to
* an {@link Image} once loaded, or reject if the image failed to load.
*
* @exports loadImage
*
* @param {String|Promise.} url The source of the image, or a promise for the URL.
* @param {Boolean} [allowCrossOrigin=true] Whether to request the image using Cross-Origin
* Resource Sharing (CORS). CORS is only actually used if the image URL is actually cross-origin.
* Data URIs are never requested using CORS.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load a single image asynchronously
* Cesium.loadImage('some/image/url.png').then(function(image) {
* // use the loaded image
* }).otherwise(function(error) {
* // an error occurred
* });
*
* // load several images in parallel
* when.all([loadImage('image1.png'), loadImage('image2.png')]).then(function(images) {
* // images is an array containing all the loaded images
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadImage(url, allowCrossOrigin) {
if (!defined(url)) {
throw new DeveloperError('url is required.');
}
allowCrossOrigin = defaultValue(allowCrossOrigin, true);
return when(url, function(url) {
var crossOrigin;
// data URIs can't have allowCrossOrigin set.
if (dataUriRegex.test(url) || !allowCrossOrigin) {
crossOrigin = false;
} else {
crossOrigin = isCrossOriginUrl(url);
}
var deferred = when.defer();
loadImage.createImage(url, crossOrigin, deferred);
return deferred.promise;
});
}
// This is broken out into a separate function so that it can be mocked for testing purposes.
loadImage.createImage = function(url, crossOrigin, deferred) {
var image = new Image();
image.onload = function() {
deferred.resolve(image);
};
image.onerror = function(e) {
deferred.reject(e);
};
if (crossOrigin) {
if (TrustedServers.contains(url)) {
image.crossOrigin = 'use-credentials';
} else {
image.crossOrigin = '';
}
}
image.src = url;
};
loadImage.defaultCreateImage = loadImage.createImage;
return loadImage;
});
/*global define*/
define('Core/throttleRequestByServer',[
'../ThirdParty/Uri',
'../ThirdParty/when',
'./defaultValue'
], function(
Uri,
when,
defaultValue) {
'use strict';
var activeRequests = {};
var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri();
function getServer(url) {
var uri = new Uri(url).resolve(pageUri);
uri.normalize();
var server = uri.authority;
if (!/:/.test(server)) {
server = server + ':' + (uri.scheme === 'https' ? '443' : '80');
}
return server;
}
/**
* Because browsers throttle the number of parallel requests allowed to each server,
* this function tracks the number of active requests in progress to each server, and
* returns undefined immediately if the request would exceed the maximum, allowing
* the caller to retry later, instead of queueing indefinitely under the browser's control.
*
* @exports throttleRequestByServer
*
* @param {String} url The URL to request.
* @param {throttleRequestByServer~RequestFunction} requestFunction The actual function that
* makes the request.
* @returns {Promise.|undefined} Either undefined, meaning the request would exceed the maximum number of
* parallel requests, or a Promise for the requested data.
*
*
* @example
* // throttle requests for an image
* var url = 'http://madeupserver.example.com/myImage.png';
* function requestFunction(url) {
* // in this simple example, loadImage could be used directly as requestFunction.
* return Cesium.loadImage(url);
* };
* var promise = Cesium.throttleRequestByServer(url, requestFunction);
* if (!Cesium.defined(promise)) {
* // too many active requests in progress, try again later.
* } else {
* promise.then(function(image) {
* // handle loaded image
* });
* }
*
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function throttleRequestByServer(url, requestFunction) {
var server = getServer(url);
var activeRequestsForServer = defaultValue(activeRequests[server], 0);
if (activeRequestsForServer >= throttleRequestByServer.maximumRequestsPerServer) {
return undefined;
}
activeRequests[server] = activeRequestsForServer + 1;
return when(requestFunction(url), function(result) {
activeRequests[server]--;
return result;
}).otherwise(function(error) {
activeRequests[server]--;
return when.reject(error);
});
}
/**
* Specifies the maximum number of requests that can be simultaneously open to a single server. If this value is higher than
* the number of requests per server actually allowed by the web browser, Cesium's ability to prioritize requests will be adversely
* affected.
* @type {Number}
* @default 6
*/
throttleRequestByServer.maximumRequestsPerServer = 6;
/**
* A function that will make a request if there are available slots to the server.
* @callback throttleRequestByServer~RequestFunction
*
* @param {String} url The url to request.
* @returns {Promise.} A promise for the requested data.
*/
return throttleRequestByServer;
});
/*global define*/
define('Core/ArcGisImageServerTerrainProvider',[
'../ThirdParty/when',
'./Credit',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Event',
'./GeographicTilingScheme',
'./getImagePixels',
'./HeightmapTerrainData',
'./loadImage',
'./Math',
'./TerrainProvider',
'./throttleRequestByServer'
], function(
when,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Event,
GeographicTilingScheme,
getImagePixels,
HeightmapTerrainData,
loadImage,
CesiumMath,
TerrainProvider,
throttleRequestByServer) {
'use strict';
/**
* A {@link TerrainProvider} that produces terrain geometry by tessellating height maps
* retrieved from an ArcGIS ImageServer.
*
* @alias ArcGisImageServerTerrainProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The URL of the ArcGIS ImageServer service.
* @param {String} [options.token] The authorization token to use to connect to the service.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
* @param {TilingScheme} [options.tilingScheme] The tiling scheme specifying how the terrain
* is broken into tiles. If this parameter is not provided, a {@link GeographicTilingScheme}
* is used.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead.
* If neither parameter is specified, the WGS84 ellipsoid is used.
* @param {Credit|String} [options.credit] The credit, which will is displayed on the canvas.
*
*
* @example
* var terrainProvider = new Cesium.ArcGisImageServerTerrainProvider({
* url : 'https://elevation.arcgisonline.com/ArcGIS/rest/services/WorldElevation/DTMEllipsoidal/ImageServer',
* token : 'KED1aF_I4UzXOHy3BnhwyBHU4l5oY6rO6walkmHoYqGp4XyIWUd5YZUC1ZrLAzvV40pR6gBXQayh0eFA8m6vPg..',
* proxy : new Cesium.DefaultProxy('/terrain/')
* });
* viewer.terrainProvider = terrainProvider;
*
* @see TerrainProvider
*/
function ArcGisImageServerTerrainProvider(options) {
if (!defined(options) || !defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
this._url = options.url;
this._token = options.token;
this._tilingScheme = options.tilingScheme;
if (!defined(this._tilingScheme)) {
this._tilingScheme = new GeographicTilingScheme({
ellipsoid : defaultValue(options.ellipsoid, Ellipsoid.WGS84)
});
}
this._heightmapWidth = 65;
this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._heightmapWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0));
this._proxy = options.proxy;
this._terrainDataStructure = {
heightScale : 1.0 / 1000.0,
heightOffset : -1000.0,
elementsPerHeight : 3,
stride : 4,
elementMultiplier : 256.0,
isBigEndian : true,
lowestEncodedHeight : 0,
highestEncodedHeight : 256 * 256 * 256 - 1
};
this._errorEvent = new Event();
var credit = options.credit;
if (typeof credit === 'string') {
credit = new Credit(credit);
}
this._credit = credit;
this._readyPromise = when.resolve(true);
}
defineProperties(ArcGisImageServerTerrainProvider.prototype, {
/**
* Gets an event that is raised when the terrain provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets the credit to display when this terrain provider is active. Typically this is used to credit
* the source of the terrain. This function should not be called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {Credit}
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {GeographicTilingScheme}
*/
tilingScheme : {
get : function() {
return this._tilingScheme;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {Boolean}
*/
ready : {
get : function() {
return true;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise;
}
},
/**
* Gets a value indicating whether or not the provider includes a water mask. The water mask
* indicates which areas of the globe are water rather than land, so they can be rendered
* as a reflective surface with animated waves. This function should not be
* called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {Boolean}
*/
hasWaterMask : {
get : function() {
return false;
}
},
/**
* Gets a value indicating whether or not the requested tiles include vertex normals.
* This function should not be called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
* @memberof ArcGisImageServerTerrainProvider.prototype
* @type {Boolean}
*/
hasVertexNormals : {
get : function() {
return false;
}
}
});
/**
* Requests the geometry for a given tile. This function should not be called before
* {@link ArcGisImageServerTerrainProvider#ready} returns true. The result includes terrain
* data and indicates that all child tiles are available.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @returns {Promise.|undefined} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
ArcGisImageServerTerrainProvider.prototype.requestTileGeometry = function(x, y, level) {
var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level);
// Each pixel in the heightmap represents the height at the center of that
// pixel. So expand the rectangle by half a sample spacing in each direction
// so that the first height is on the edge of the rectangle we need rather than
// half a sample spacing into the rectangle.
var xSpacing = (rectangle.east - rectangle.west) / (this._heightmapWidth - 1);
var ySpacing = (rectangle.north - rectangle.south) / (this._heightmapWidth - 1);
rectangle.west -= xSpacing * 0.5;
rectangle.east += xSpacing * 0.5;
rectangle.south -= ySpacing * 0.5;
rectangle.north += ySpacing * 0.5;
var bbox = CesiumMath.toDegrees(rectangle.west) + '%2C' + CesiumMath.toDegrees(rectangle.south) + '%2C' + CesiumMath.toDegrees(rectangle.east) + '%2C' + CesiumMath.toDegrees(rectangle.north);
var url = this._url + '/exportImage?interpolation=RSP_BilinearInterpolation&format=tiff&f=image&size=' + this._heightmapWidth + '%2C' + this._heightmapWidth + '&bboxSR=4326&imageSR=4326&bbox=' + bbox;
if (this._token) {
url += '&token=' + this._token;
}
var proxy = this._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
var promise = throttleRequestByServer(url, loadImage);
if (!defined(promise)) {
return undefined;
}
var that = this;
return when(promise, function(image) {
return new HeightmapTerrainData({
buffer : getImagePixels(image),
width : that._heightmapWidth,
height : that._heightmapWidth,
childTileMask : 15, // all children present
structure : that._terrainDataStructure
});
});
};
/**
* Gets the maximum geometric error allowed in a tile at a given level.
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error.
*/
ArcGisImageServerTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
return this._levelZeroMaximumGeometricError / (1 << level);
};
/**
* Determines whether data for a tile is available to be loaded.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @returns {Boolean} Undefined if not supported, otherwise true or false.
*/
ArcGisImageServerTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
return undefined;
};
return ArcGisImageServerTerrainProvider;
});
/*global define*/
define('Core/arrayRemoveDuplicates',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./Math'
], function(
defaultValue,
defined,
DeveloperError,
CesiumMath) {
'use strict';
var removeDuplicatesEpsilon = CesiumMath.EPSILON10;
/**
* Removes adjacent duplicate values in an array of values.
*
* @param {Object[]} [values] The array of values.
* @param {Function} equalsEpsilon Function to compare values with an epsilon. Boolean equalsEpsilon(left, right, epsilon).
* @param {Boolean} [wrapAround=false] Compare the last value in the array against the first value.
* @returns {Object[]|undefined} A new array of values with no adjacent duplicate values or the input array if no duplicates were found.
*
* @example
* // Returns [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0), (3.0, 3.0, 3.0), (1.0, 1.0, 1.0)]
* var values = [
* new Cesium.Cartesian3(1.0, 1.0, 1.0),
* new Cesium.Cartesian3(1.0, 1.0, 1.0),
* new Cesium.Cartesian3(2.0, 2.0, 2.0),
* new Cesium.Cartesian3(3.0, 3.0, 3.0),
* new Cesium.Cartesian3(1.0, 1.0, 1.0)];
* var nonDuplicatevalues = Cesium.PolylinePipeline.removeDuplicates(values, Cartesian3.equalsEpsilon);
*
* @example
* // Returns [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0), (3.0, 3.0, 3.0)]
* var values = [
* new Cesium.Cartesian3(1.0, 1.0, 1.0),
* new Cesium.Cartesian3(1.0, 1.0, 1.0),
* new Cesium.Cartesian3(2.0, 2.0, 2.0),
* new Cesium.Cartesian3(3.0, 3.0, 3.0),
* new Cesium.Cartesian3(1.0, 1.0, 1.0)];
* var nonDuplicatevalues = Cesium.PolylinePipeline.removeDuplicates(values, Cartesian3.equalsEpsilon, true);
*
* @private
*/
function arrayRemoveDuplicates(values, equalsEpsilon, wrapAround) {
if (!defined(equalsEpsilon)) {
throw new DeveloperError('equalsEpsilon is required.');
}
if (!defined(values)) {
return undefined;
}
wrapAround = defaultValue(wrapAround, false);
var length = values.length;
if (length < 2) {
return values;
}
var i;
var v0;
var v1;
for (i = 1; i < length; ++i) {
v0 = values[i - 1];
v1 = values[i];
if (equalsEpsilon(v0, v1, removeDuplicatesEpsilon)) {
break;
}
}
if (i === length) {
if (wrapAround && equalsEpsilon(values[0], values[values.length - 1], removeDuplicatesEpsilon)) {
return values.slice(1);
}
return values;
}
var cleanedvalues = values.slice(0, i);
for (; i < length; ++i) {
// v0 is set by either the previous loop, or the previous clean point.
v1 = values[i];
if (!equalsEpsilon(v0, v1, removeDuplicatesEpsilon)) {
cleanedvalues.push(v1);
v0 = v1;
}
}
if (wrapAround && cleanedvalues.length > 1 && equalsEpsilon(cleanedvalues[0], cleanedvalues[cleanedvalues.length - 1], removeDuplicatesEpsilon)) {
cleanedvalues.shift();
}
return cleanedvalues;
}
return arrayRemoveDuplicates;
});
/*global define*/
define('Core/AssociativeArray',[
'./defined',
'./defineProperties',
'./DeveloperError'
], function(
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* A collection of key-value pairs that is stored as a hash for easy
* lookup but also provides an array for fast iteration.
* @alias AssociativeArray
* @constructor
*/
function AssociativeArray() {
this._array = [];
this._hash = {};
}
defineProperties(AssociativeArray.prototype, {
/**
* Gets the number of items in the collection.
* @memberof AssociativeArray.prototype
*
* @type {Number}
*/
length : {
get : function() {
return this._array.length;
}
},
/**
* Gets an unordered array of all values in the collection.
* This is a live array that will automatically reflect the values in the collection,
* it should not be modified directly.
* @memberof AssociativeArray.prototype
*
* @type {Array}
*/
values : {
get : function() {
return this._array;
}
}
});
/**
* Determines if the provided key is in the array.
*
* @param {String|Number} key The key to check.
* @returns {Boolean} true
if the key is in the array, false
otherwise.
*/
AssociativeArray.prototype.contains = function(key) {
if (typeof key !== 'string' && typeof key !== 'number') {
throw new DeveloperError('key is required to be a string or number.');
}
return defined(this._hash[key]);
};
/**
* Associates the provided key with the provided value. If the key already
* exists, it is overwritten with the new value.
*
* @param {String|Number} key A unique identifier.
* @param {Object} value The value to associate with the provided key.
*/
AssociativeArray.prototype.set = function(key, value) {
if (typeof key !== 'string' && typeof key !== 'number') {
throw new DeveloperError('key is required to be a string or number.');
}
var oldValue = this._hash[key];
if (value !== oldValue) {
this.remove(key);
this._hash[key] = value;
this._array.push(value);
}
};
/**
* Retrieves the value associated with the provided key.
*
* @param {String|Number} key The key whose value is to be retrieved.
* @returns {Object} The associated value, or undefined if the key does not exist in the collection.
*/
AssociativeArray.prototype.get = function(key) {
if (typeof key !== 'string' && typeof key !== 'number') {
throw new DeveloperError('key is required to be a string or number.');
}
return this._hash[key];
};
/**
* Removes a key-value pair from the collection.
*
* @param {String|Number} key The key to be removed.
* @returns {Boolean} True if it was removed, false if the key was not in the collection.
*/
AssociativeArray.prototype.remove = function(key) {
if (defined(key) && typeof key !== 'string' && typeof key !== 'number') {
throw new DeveloperError('key is required to be a string or number.');
}
var value = this._hash[key];
var hasValue = defined(value);
if (hasValue) {
var array = this._array;
array.splice(array.indexOf(value), 1);
delete this._hash[key];
}
return hasValue;
};
/**
* Clears the collection.
*/
AssociativeArray.prototype.removeAll = function() {
var array = this._array;
if (array.length > 0) {
this._hash = {};
array.length = 0;
}
};
return AssociativeArray;
});
/*global define*/
define('Core/barycentricCoordinates',[
'./Cartesian2',
'./Cartesian3',
'./defined',
'./DeveloperError'
], function(
Cartesian2,
Cartesian3,
defined,
DeveloperError) {
'use strict';
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
/**
* Computes the barycentric coordinates for a point with respect to a triangle.
*
* @exports barycentricCoordinates
*
* @param {Cartesian2|Cartesian3} point The point to test.
* @param {Cartesian2|Cartesian3} p0 The first point of the triangle, corresponding to the barycentric x-axis.
* @param {Cartesian2|Cartesian3} p1 The second point of the triangle, corresponding to the barycentric y-axis.
* @param {Cartesian2|Cartesian3} p2 The third point of the triangle, corresponding to the barycentric z-axis.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
*
* @example
* // Returns Cartesian3.UNIT_X
* var p = new Cesium.Cartesian3(-1.0, 0.0, 0.0);
* var b = Cesium.barycentricCoordinates(p,
* new Cesium.Cartesian3(-1.0, 0.0, 0.0),
* new Cesium.Cartesian3( 1.0, 0.0, 0.0),
* new Cesium.Cartesian3( 0.0, 1.0, 1.0));
*/
function barycentricCoordinates(point, p0, p1, p2, result) {
if (!defined(point) || !defined(p0) || !defined(p1) || !defined(p2)) {
throw new DeveloperError('point, p0, p1, and p2 are required.');
}
if (!defined(result)) {
result = new Cartesian3();
}
// Implementation based on http://www.blackpawn.com/texts/pointinpoly/default.html.
var v0, v1, v2;
var dot00, dot01, dot02, dot11, dot12;
if(!defined(p0.z)) {
v0 = Cartesian2.subtract(p1, p0, scratchCartesian1);
v1 = Cartesian2.subtract(p2, p0, scratchCartesian2);
v2 = Cartesian2.subtract(point, p0, scratchCartesian3);
dot00 = Cartesian2.dot(v0, v0);
dot01 = Cartesian2.dot(v0, v1);
dot02 = Cartesian2.dot(v0, v2);
dot11 = Cartesian2.dot(v1, v1);
dot12 = Cartesian2.dot(v1, v2);
} else {
v0 = Cartesian3.subtract(p1, p0, scratchCartesian1);
v1 = Cartesian3.subtract(p2, p0, scratchCartesian2);
v2 = Cartesian3.subtract(point, p0, scratchCartesian3);
dot00 = Cartesian3.dot(v0, v0);
dot01 = Cartesian3.dot(v0, v1);
dot02 = Cartesian3.dot(v0, v2);
dot11 = Cartesian3.dot(v1, v1);
dot12 = Cartesian3.dot(v1, v2);
}
var q = 1.0 / (dot00 * dot11 - dot01 * dot01);
result.y = (dot11 * dot02 - dot01 * dot12) * q;
result.z = (dot00 * dot12 - dot01 * dot02) * q;
result.x = 1.0 - result.y - result.z;
return result;
}
return barycentricCoordinates;
});
/*global define*/
define('Core/BingMapsApi',[
'./Credit',
'./defined'
], function(
Credit,
defined) {
'use strict';
/**
* Object for setting and retrieving the default BingMaps API key.
*
* @exports BingMapsApi
*/
var BingMapsApi = {
};
/**
* The default Bing Maps API key to use if one is not provided to the
* constructor of an object that uses the Bing Maps API. If this property is undefined,
* Cesium's default key is used, which is only suitable for use early in development.
* Please generate your own key by visiting
* {@link https://www.bingmapsportal.com/}
* as soon as possible and prior to deployment. When Cesium's default key is used,
* a message is printed to the console the first time the Bing Maps API is used.
*
* @type {String}
*/
BingMapsApi.defaultKey = undefined;
var printedBingWarning = false;
var errorCredit;
var errorString = 'This application is using Cesium\'s default Bing Maps key. Please create a new key for the application as soon as possible and prior to deployment by visiting https://www.bingmapsportal.com/, and provide your key to Cesium by setting the Cesium.BingMapsApi.defaultKey property before constructing the CesiumWidget or any other object that uses the Bing Maps API.';
BingMapsApi.getKey = function(providedKey) {
if (defined(providedKey)) {
return providedKey;
}
if (!defined(BingMapsApi.defaultKey)) {
if (!printedBingWarning) {
console.log(errorString);
printedBingWarning = true;
}
return 'AhiQlDaPOwKbStA_3nJIdimUj4PRYkp0yHwcNpvxVlLNPRo5ZJWY5oX_h6B_dMbm';
}
return BingMapsApi.defaultKey;
};
BingMapsApi.getErrorCredit = function(providedKey) {
if (defined(providedKey) || defined(BingMapsApi.defaultKey)) {
return undefined;
}
if (!defined(errorCredit)) {
errorCredit = new Credit(errorString);
}
return errorCredit;
};
return BingMapsApi;
});
/*global define*/
define('Core/BoundingRectangle',[
'./Cartesian2',
'./Cartographic',
'./defaultValue',
'./defined',
'./DeveloperError',
'./GeographicProjection',
'./Intersect',
'./Rectangle'
], function(
Cartesian2,
Cartographic,
defaultValue,
defined,
DeveloperError,
GeographicProjection,
Intersect,
Rectangle) {
'use strict';
/**
* A bounding rectangle given by a corner, width and height.
* @alias BoundingRectangle
* @constructor
*
* @param {Number} [x=0.0] The x coordinate of the rectangle.
* @param {Number} [y=0.0] The y coordinate of the rectangle.
* @param {Number} [width=0.0] The width of the rectangle.
* @param {Number} [height=0.0] The height of the rectangle.
*
* @see BoundingSphere
* @see Packable
*/
function BoundingRectangle(x, y, width, height) {
/**
* The x coordinate of the rectangle.
* @type {Number}
* @default 0.0
*/
this.x = defaultValue(x, 0.0);
/**
* The y coordinate of the rectangle.
* @type {Number}
* @default 0.0
*/
this.y = defaultValue(y, 0.0);
/**
* The width of the rectangle.
* @type {Number}
* @default 0.0
*/
this.width = defaultValue(width, 0.0);
/**
* The height of the rectangle.
* @type {Number}
* @default 0.0
*/
this.height = defaultValue(height, 0.0);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
BoundingRectangle.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {BoundingRectangle} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
BoundingRectangle.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.x;
array[startingIndex++] = value.y;
array[startingIndex++] = value.width;
array[startingIndex] = value.height;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {BoundingRectangle} [result] The object into which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BoundingRectangle.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new BoundingRectangle();
}
result.x = array[startingIndex++];
result.y = array[startingIndex++];
result.width = array[startingIndex++];
result.height = array[startingIndex];
return result;
};
/**
* Computes a bounding rectangle enclosing the list of 2D points.
* The rectangle is oriented with the corner at the bottom left.
*
* @param {Cartesian2[]} positions List of points that the bounding rectangle will enclose. Each point must have x
and y
properties.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BoundingRectangle.fromPoints = function(positions, result) {
if (!defined(result)) {
result = new BoundingRectangle();
}
if (!defined(positions) || positions.length === 0) {
result.x = 0;
result.y = 0;
result.width = 0;
result.height = 0;
return result;
}
var length = positions.length;
var minimumX = positions[0].x;
var minimumY = positions[0].y;
var maximumX = positions[0].x;
var maximumY = positions[0].y;
for ( var i = 1; i < length; i++) {
var p = positions[i];
var x = p.x;
var y = p.y;
minimumX = Math.min(x, minimumX);
maximumX = Math.max(x, maximumX);
minimumY = Math.min(y, minimumY);
maximumY = Math.max(y, maximumY);
}
result.x = minimumX;
result.y = minimumY;
result.width = maximumX - minimumX;
result.height = maximumY - minimumY;
return result;
};
var defaultProjection = new GeographicProjection();
var fromRectangleLowerLeft = new Cartographic();
var fromRectangleUpperRight = new Cartographic();
/**
* Computes a bounding rectangle from an rectangle.
*
* @param {Rectangle} rectangle The valid rectangle used to create a bounding rectangle.
* @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BoundingRectangle.fromRectangle = function(rectangle, projection, result) {
if (!defined(result)) {
result = new BoundingRectangle();
}
if (!defined(rectangle)) {
result.x = 0;
result.y = 0;
result.width = 0;
result.height = 0;
return result;
}
projection = defaultValue(projection, defaultProjection);
var lowerLeft = projection.project(Rectangle.southwest(rectangle, fromRectangleLowerLeft));
var upperRight = projection.project(Rectangle.northeast(rectangle, fromRectangleUpperRight));
Cartesian2.subtract(upperRight, lowerLeft, upperRight);
result.x = lowerLeft.x;
result.y = lowerLeft.y;
result.width = upperRight.x;
result.height = upperRight.y;
return result;
};
/**
* Duplicates a BoundingRectangle instance.
*
* @param {BoundingRectangle} rectangle The bounding rectangle to duplicate.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided. (Returns undefined if rectangle is undefined)
*/
BoundingRectangle.clone = function(rectangle, result) {
if (!defined(rectangle)) {
return undefined;
}
if (!defined(result)) {
return new BoundingRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
result.x = rectangle.x;
result.y = rectangle.y;
result.width = rectangle.width;
result.height = rectangle.height;
return result;
};
/**
* Computes a bounding rectangle that is the union of the left and right bounding rectangles.
*
* @param {BoundingRectangle} left A rectangle to enclose in bounding rectangle.
* @param {BoundingRectangle} right A rectangle to enclose in a bounding rectangle.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BoundingRectangle.union = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
if (!defined(result)) {
result = new BoundingRectangle();
}
var lowerLeftX = Math.min(left.x, right.x);
var lowerLeftY = Math.min(left.y, right.y);
var upperRightX = Math.max(left.x + left.width, right.x + right.width);
var upperRightY = Math.max(left.y + left.height, right.y + right.height);
result.x = lowerLeftX;
result.y = lowerLeftY;
result.width = upperRightX - lowerLeftX;
result.height = upperRightY - lowerLeftY;
return result;
};
/**
* Computes a bounding rectangle by enlarging the provided rectangle until it contains the provided point.
*
* @param {BoundingRectangle} rectangle A rectangle to expand.
* @param {Cartesian2} point A point to enclose in a bounding rectangle.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BoundingRectangle.expand = function(rectangle, point, result) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
if (!defined(point)) {
throw new DeveloperError('point is required.');
}
result = BoundingRectangle.clone(rectangle, result);
var width = point.x - result.x;
var height = point.y - result.y;
if (width > result.width) {
result.width = width;
} else if (width < 0) {
result.width -= width;
result.x = point.x;
}
if (height > result.height) {
result.height = height;
} else if (height < 0) {
result.height -= height;
result.y = point.y;
}
return result;
};
/**
* Determines if two rectangles intersect.
*
* @param {BoundingRectangle} left A rectangle to check for intersection.
* @param {BoundingRectangle} right The other rectangle to check for intersection.
* @returns {Intersect} Intersect.INTESECTING
if the rectangles intersect, Intersect.OUTSIDE
otherwise.
*/
BoundingRectangle.intersect = function(left, right) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(right)) {
throw new DeveloperError('right is required.');
}
var leftX = left.x;
var leftY = left.y;
var rightX = right.x;
var rightY = right.y;
if (!(leftX > rightX + right.width ||
leftX + left.width < rightX ||
leftY + left.height < rightY ||
leftY > rightY + right.height)) {
return Intersect.INTERSECTING;
}
return Intersect.OUTSIDE;
};
/**
* Compares the provided BoundingRectangles componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {BoundingRectangle} [left] The first BoundingRectangle.
* @param {BoundingRectangle} [right] The second BoundingRectangle.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
BoundingRectangle.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.x === right.x) &&
(left.y === right.y) &&
(left.width === right.width) &&
(left.height === right.height));
};
/**
* Duplicates this BoundingRectangle instance.
*
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BoundingRectangle.prototype.clone = function(result) {
return BoundingRectangle.clone(this, result);
};
/**
* Determines if this rectangle intersects with another.
*
* @param {BoundingRectangle} right A rectangle to check for intersection.
* @returns {Intersect} Intersect.INTESECTING
if the rectangles intersect, Intersect.OUTSIDE
otherwise.
*/
BoundingRectangle.prototype.intersect = function(right) {
return BoundingRectangle.intersect(this, right);
};
/**
* Compares this BoundingRectangle against the provided BoundingRectangle componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {BoundingRectangle} [right] The right hand side BoundingRectangle.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
BoundingRectangle.prototype.equals = function(right) {
return BoundingRectangle.equals(this, right);
};
return BoundingRectangle;
});
/*global define*/
define('Core/GeometryType',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* @private
*/
var GeometryType = {
NONE : 0,
TRIANGLES : 1,
LINES : 2,
POLYLINES : 3
};
return freezeObject(GeometryType);
});
/*global define*/
define('Core/PrimitiveType',[
'./freezeObject',
'./WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* The type of a geometric primitive, i.e., points, lines, and triangles.
*
* @exports PrimitiveType
*/
var PrimitiveType = {
/**
* Points primitive where each vertex (or index) is a separate point.
*
* @type {Number}
* @constant
*/
POINTS : WebGLConstants.POINTS,
/**
* Lines primitive where each two vertices (or indices) is a line segment. Line segments are not necessarily connected.
*
* @type {Number}
* @constant
*/
LINES : WebGLConstants.LINES,
/**
* Line loop primitive where each vertex (or index) after the first connects a line to
* the previous vertex, and the last vertex implicitly connects to the first.
*
* @type {Number}
* @constant
*/
LINE_LOOP : WebGLConstants.LINE_LOOP,
/**
* Line strip primitive where each vertex (or index) after the first connects a line to the previous vertex.
*
* @type {Number}
* @constant
*/
LINE_STRIP : WebGLConstants.LINE_STRIP,
/**
* Triangles primitive where each three vertices (or indices) is a triangle. Triangles do not necessarily share edges.
*
* @type {Number}
* @constant
*/
TRIANGLES : WebGLConstants.TRIANGLES,
/**
* Triangle strip primitive where each vertex (or index) after the first two connect to
* the previous two vertices forming a triangle. For example, this can be used to model a wall.
*
* @type {Number}
* @constant
*/
TRIANGLE_STRIP : WebGLConstants.TRIANGLE_STRIP,
/**
* Triangle fan primitive where each vertex (or index) after the first two connect to
* the previous vertex and the first vertex forming a triangle. For example, this can be used
* to model a cone or circle.
*
* @type {Number}
* @constant
*/
TRIANGLE_FAN : WebGLConstants.TRIANGLE_FAN,
/**
* @private
*/
validate : function(primitiveType) {
return primitiveType === PrimitiveType.POINTS ||
primitiveType === PrimitiveType.LINES ||
primitiveType === PrimitiveType.LINE_LOOP ||
primitiveType === PrimitiveType.LINE_STRIP ||
primitiveType === PrimitiveType.TRIANGLES ||
primitiveType === PrimitiveType.TRIANGLE_STRIP ||
primitiveType === PrimitiveType.TRIANGLE_FAN;
}
};
return freezeObject(PrimitiveType);
});
/*global define*/
define('Core/Geometry',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./GeometryType',
'./PrimitiveType'
], function(
defaultValue,
defined,
DeveloperError,
GeometryType,
PrimitiveType) {
'use strict';
/**
* A geometry representation with attributes forming vertices and optional index data
* defining primitives. Geometries and an {@link Appearance}, which describes the shading,
* can be assigned to a {@link Primitive} for visualization. A Primitive
can
* be created from many heterogeneous - in many cases - geometries for performance.
*
* Geometries can be transformed and optimized using functions in {@link GeometryPipeline}.
*
*
* @alias Geometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {GeometryAttributes} options.attributes Attributes, which make up the geometry's vertices.
* @param {PrimitiveType} [options.primitiveType=PrimitiveType.TRIANGLES] The type of primitives in the geometry.
* @param {Uint16Array|Uint32Array} [options.indices] Optional index data that determines the primitives in the geometry.
* @param {BoundingSphere} [options.boundingSphere] An optional bounding sphere that fully enclosed the geometry.
*
* @see PolygonGeometry
* @see RectangleGeometry
* @see EllipseGeometry
* @see CircleGeometry
* @see WallGeometry
* @see SimplePolylineGeometry
* @see BoxGeometry
* @see EllipsoidGeometry
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Geometry%20and%20Appearances.html|Geometry and Appearances Demo}
*
* @example
* // Create geometry with a position attribute and indexed lines.
* var positions = new Float64Array([
* 0.0, 0.0, 0.0,
* 7500000.0, 0.0, 0.0,
* 0.0, 7500000.0, 0.0
* ]);
*
* var geometry = new Cesium.Geometry({
* attributes : {
* position : new Cesium.GeometryAttribute({
* componentDatatype : Cesium.ComponentDatatype.DOUBLE,
* componentsPerAttribute : 3,
* values : positions
* })
* },
* indices : new Uint16Array([0, 1, 1, 2, 2, 0]),
* primitiveType : Cesium.PrimitiveType.LINES,
* boundingSphere : Cesium.BoundingSphere.fromVertices(positions)
* });
*/
function Geometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.attributes)) {
throw new DeveloperError('options.attributes is required.');
}
/**
* Attributes, which make up the geometry's vertices. Each property in this object corresponds to a
* {@link GeometryAttribute} containing the attribute's data.
*
* Attributes are always stored non-interleaved in a Geometry.
*
*
* There are reserved attribute names with well-known semantics. The following attributes
* are created by a Geometry (depending on the provided {@link VertexFormat}.
*
* position
- 3D vertex position. 64-bit floating-point (for precision). 3 components per attribute. See {@link VertexFormat#position}.
* normal
- Normal (normalized), commonly used for lighting. 32-bit floating-point. 3 components per attribute. See {@link VertexFormat#normal}.
* st
- 2D texture coordinate. 32-bit floating-point. 2 components per attribute. See {@link VertexFormat#st}.
* binormal
- Binormal (normalized), used for tangent-space effects like bump mapping. 32-bit floating-point. 3 components per attribute. See {@link VertexFormat#binormal}.
* tangent
- Tangent (normalized), used for tangent-space effects like bump mapping. 32-bit floating-point. 3 components per attribute. See {@link VertexFormat#tangent}.
*
*
*
* The following attribute names are generally not created by a Geometry, but are added
* to a Geometry by a {@link Primitive} or {@link GeometryPipeline} functions to prepare
* the geometry for rendering.
*
* position3DHigh
- High 32 bits for encoded 64-bit position computed with {@link GeometryPipeline.encodeAttribute}. 32-bit floating-point. 4 components per attribute.
* position3DLow
- Low 32 bits for encoded 64-bit position computed with {@link GeometryPipeline.encodeAttribute}. 32-bit floating-point. 4 components per attribute.
* position3DHigh
- High 32 bits for encoded 64-bit 2D (Columbus view) position computed with {@link GeometryPipeline.encodeAttribute}. 32-bit floating-point. 4 components per attribute.
* position2DLow
- Low 32 bits for encoded 64-bit 2D (Columbus view) position computed with {@link GeometryPipeline.encodeAttribute}. 32-bit floating-point. 4 components per attribute.
* color
- RGBA color (normalized) usually from {@link GeometryInstance#color}. 32-bit floating-point. 4 components per attribute.
* pickColor
- RGBA color used for picking. 32-bit floating-point. 4 components per attribute.
*
*
*
* @type GeometryAttributes
*
* @default undefined
*
*
* @example
* geometry.attributes.position = new Cesium.GeometryAttribute({
* componentDatatype : Cesium.ComponentDatatype.FLOAT,
* componentsPerAttribute : 3,
* values : new Float32Array(0)
* });
*
* @see GeometryAttribute
* @see VertexFormat
*/
this.attributes = options.attributes;
/**
* Optional index data that - along with {@link Geometry#primitiveType} -
* determines the primitives in the geometry.
*
* @type Array
*
* @default undefined
*/
this.indices = options.indices;
/**
* The type of primitives in the geometry. This is most often {@link PrimitiveType.TRIANGLES},
* but can varying based on the specific geometry.
*
* @type PrimitiveType
*
* @default undefined
*/
this.primitiveType = defaultValue(options.primitiveType, PrimitiveType.TRIANGLES);
/**
* An optional bounding sphere that fully encloses the geometry. This is
* commonly used for culling.
*
* @type BoundingSphere
*
* @default undefined
*/
this.boundingSphere = options.boundingSphere;
/**
* @private
*/
this.geometryType = defaultValue(options.geometryType, GeometryType.NONE);
/**
* @private
*/
this.boundingSphereCV = options.boundingSphereCV;
}
/**
* Computes the number of vertices in a geometry. The runtime is linear with
* respect to the number of attributes in a vertex, not the number of vertices.
*
* @param {Geometry} geometry The geometry.
* @returns {Number} The number of vertices in the geometry.
*
* @example
* var numVertices = Cesium.Geometry.computeNumberOfVertices(geometry);
*/
Geometry.computeNumberOfVertices = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
var numberOfVertices = -1;
for ( var property in geometry.attributes) {
if (geometry.attributes.hasOwnProperty(property) &&
defined(geometry.attributes[property]) &&
defined(geometry.attributes[property].values)) {
var attribute = geometry.attributes[property];
var num = attribute.values.length / attribute.componentsPerAttribute;
if ((numberOfVertices !== num) && (numberOfVertices !== -1)) {
throw new DeveloperError('All attribute lists must have the same number of attributes.');
}
numberOfVertices = num;
}
}
return numberOfVertices;
};
return Geometry;
});
/*global define*/
define('Core/GeometryAttribute',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Values and type information for geometry attributes. A {@link Geometry}
* generally contains one or more attributes. All attributes together form
* the geometry's vertices.
*
* @alias GeometryAttribute
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {ComponentDatatype} [options.componentDatatype] The datatype of each component in the attribute, e.g., individual elements in values.
* @param {Number} [options.componentsPerAttribute] A number between 1 and 4 that defines the number of components in an attributes.
* @param {Boolean} [options.normalize=false] When true
and componentDatatype
is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering.
* @param {TypedArray} [options.values] The values for the attributes stored in a typed array.
*
* @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4.
*
*
* @example
* var geometry = new Cesium.Geometry({
* attributes : {
* position : new Cesium.GeometryAttribute({
* componentDatatype : Cesium.ComponentDatatype.FLOAT,
* componentsPerAttribute : 3,
* values : new Float32Array([
* 0.0, 0.0, 0.0,
* 7500000.0, 0.0, 0.0,
* 0.0, 7500000.0, 0.0
* ])
* })
* },
* primitiveType : Cesium.PrimitiveType.LINE_LOOP
* });
*
* @see Geometry
*/
function GeometryAttribute(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.componentDatatype)) {
throw new DeveloperError('options.componentDatatype is required.');
}
if (!defined(options.componentsPerAttribute)) {
throw new DeveloperError('options.componentsPerAttribute is required.');
}
if (options.componentsPerAttribute < 1 || options.componentsPerAttribute > 4) {
throw new DeveloperError('options.componentsPerAttribute must be between 1 and 4.');
}
if (!defined(options.values)) {
throw new DeveloperError('options.values is required.');
}
/**
* The datatype of each component in the attribute, e.g., individual elements in
* {@link GeometryAttribute#values}.
*
* @type ComponentDatatype
*
* @default undefined
*/
this.componentDatatype = options.componentDatatype;
/**
* A number between 1 and 4 that defines the number of components in an attributes.
* For example, a position attribute with x, y, and z components would have 3 as
* shown in the code example.
*
* @type Number
*
* @default undefined
*
* @example
* attribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
* attribute.componentsPerAttribute = 3;
* attribute.values = new Float32Array([
* 0.0, 0.0, 0.0,
* 7500000.0, 0.0, 0.0,
* 0.0, 7500000.0, 0.0
* ]);
*/
this.componentsPerAttribute = options.componentsPerAttribute;
/**
* When true
and componentDatatype
is an integer format,
* indicate that the components should be mapped to the range [0, 1] (unsigned)
* or [-1, 1] (signed) when they are accessed as floating-point for rendering.
*
* This is commonly used when storing colors using {@link ComponentDatatype.UNSIGNED_BYTE}.
*
*
* @type Boolean
*
* @default false
*
* @example
* attribute.componentDatatype = Cesium.ComponentDatatype.UNSIGNED_BYTE;
* attribute.componentsPerAttribute = 4;
* attribute.normalize = true;
* attribute.values = new Uint8Array([
* Cesium.Color.floatToByte(color.red),
* Cesium.Color.floatToByte(color.green),
* Cesium.Color.floatToByte(color.blue),
* Cesium.Color.floatToByte(color.alpha)
* ]);
*/
this.normalize = defaultValue(options.normalize, false);
/**
* The values for the attributes stored in a typed array. In the code example,
* every three elements in values
defines one attributes since
* componentsPerAttribute
is 3.
*
* @type TypedArray
*
* @default undefined
*
* @example
* attribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
* attribute.componentsPerAttribute = 3;
* attribute.values = new Float32Array([
* 0.0, 0.0, 0.0,
* 7500000.0, 0.0, 0.0,
* 0.0, 7500000.0, 0.0
* ]);
*/
this.values = options.values;
}
return GeometryAttribute;
});
/*global define*/
define('Core/GeometryAttributes',[
'./defaultValue'
], function(
defaultValue) {
'use strict';
/**
* Attributes, which make up a geometry's vertices. Each property in this object corresponds to a
* {@link GeometryAttribute} containing the attribute's data.
*
* Attributes are always stored non-interleaved in a Geometry.
*
*
* @alias GeometryAttributes
* @constructor
*/
function GeometryAttributes(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The 3D position attribute.
*
* 64-bit floating-point (for precision). 3 components per attribute.
*
*
* @type GeometryAttribute
*
* @default undefined
*/
this.position = options.position;
/**
* The normal attribute (normalized), which is commonly used for lighting.
*
* 32-bit floating-point. 3 components per attribute.
*
*
* @type GeometryAttribute
*
* @default undefined
*/
this.normal = options.normal;
/**
* The 2D texture coordinate attribute.
*
* 32-bit floating-point. 2 components per attribute
*
*
* @type GeometryAttribute
*
* @default undefined
*/
this.st = options.st;
/**
* The binormal attribute (normalized), which is used for tangent-space effects like bump mapping.
*
* 32-bit floating-point. 3 components per attribute.
*
*
* @type GeometryAttribute
*
* @default undefined
*/
this.binormal = options.binormal;
/**
* The tangent attribute (normalized), which is used for tangent-space effects like bump mapping.
*
* 32-bit floating-point. 3 components per attribute.
*
*
* @type GeometryAttribute
*
* @default undefined
*/
this.tangent = options.tangent;
/**
* The color attribute.
*
* 8-bit unsigned integer. 4 components per attribute.
*
*
* @type GeometryAttribute
*
* @default undefined
*/
this.color = options.color;
}
return GeometryAttributes;
});
/*global define*/
define('Core/VertexFormat',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./freezeObject'
], function(
defaultValue,
defined,
DeveloperError,
freezeObject) {
'use strict';
/**
* A vertex format defines what attributes make up a vertex. A VertexFormat can be provided
* to a {@link Geometry} to request that certain properties be computed, e.g., just position,
* position and normal, etc.
*
* @param {Object} [options] An object with boolean properties corresponding to VertexFormat properties as shown in the code example.
*
* @alias VertexFormat
* @constructor
*
* @example
* // Create a vertex format with position and 2D texture coordinate attributes.
* var format = new Cesium.VertexFormat({
* position : true,
* st : true
* });
*
* @see Geometry#attributes
* @see Packable
*/
function VertexFormat(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* When true
, the vertex has a 3D position attribute.
*
* 64-bit floating-point (for precision). 3 components per attribute.
*
*
* @type Boolean
*
* @default false
*/
this.position = defaultValue(options.position, false);
/**
* When true
, the vertex has a normal attribute (normalized), which is commonly used for lighting.
*
* 32-bit floating-point. 3 components per attribute.
*
*
* @type Boolean
*
* @default false
*/
this.normal = defaultValue(options.normal, false);
/**
* When true
, the vertex has a 2D texture coordinate attribute.
*
* 32-bit floating-point. 2 components per attribute
*
*
* @type Boolean
*
* @default false
*/
this.st = defaultValue(options.st, false);
/**
* When true
, the vertex has a binormal attribute (normalized), which is used for tangent-space effects like bump mapping.
*
* 32-bit floating-point. 3 components per attribute.
*
*
* @type Boolean
*
* @default false
*/
this.binormal = defaultValue(options.binormal, false);
/**
* When true
, the vertex has a tangent attribute (normalized), which is used for tangent-space effects like bump mapping.
*
* 32-bit floating-point. 3 components per attribute.
*
*
* @type Boolean
*
* @default false
*/
this.tangent = defaultValue(options.tangent, false);
/**
* When true
, the vertex has an RGB color attribute.
*
* 8-bit unsigned byte. 3 components per attribute.
*
*
* @type Boolean
*
* @default false
*/
this.color = defaultValue(options.color, false);
}
/**
* An immutable vertex format with only a position attribute.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
*/
VertexFormat.POSITION_ONLY = freezeObject(new VertexFormat({
position : true
}));
/**
* An immutable vertex format with position and normal attributes.
* This is compatible with per-instance color appearances like {@link PerInstanceColorAppearance}.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
* @see VertexFormat#normal
*/
VertexFormat.POSITION_AND_NORMAL = freezeObject(new VertexFormat({
position : true,
normal : true
}));
/**
* An immutable vertex format with position, normal, and st attributes.
* This is compatible with {@link MaterialAppearance} when {@link MaterialAppearance#materialSupport}
* is TEXTURED/code>.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
* @see VertexFormat#normal
* @see VertexFormat#st
*/
VertexFormat.POSITION_NORMAL_AND_ST = freezeObject(new VertexFormat({
position : true,
normal : true,
st : true
}));
/**
* An immutable vertex format with position and st attributes.
* This is compatible with {@link EllipsoidSurfaceAppearance}.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
* @see VertexFormat#st
*/
VertexFormat.POSITION_AND_ST = freezeObject(new VertexFormat({
position : true,
st : true
}));
/**
* An immutable vertex format with position and color attributes.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
* @see VertexFormat#color
*/
VertexFormat.POSITION_AND_COLOR = freezeObject(new VertexFormat({
position : true,
color : true
}));
/**
* An immutable vertex format with well-known attributes: position, normal, st, binormal, and tangent.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
* @see VertexFormat#normal
* @see VertexFormat#st
* @see VertexFormat#binormal
* @see VertexFormat#tangent
*/
VertexFormat.ALL = freezeObject(new VertexFormat({
position : true,
normal : true,
st : true,
binormal : true,
tangent : true
}));
/**
* An immutable vertex format with position, normal, and st attributes.
* This is compatible with most appearances and materials; however
* normal and st attributes are not always required. When this is
* known in advance, another VertexFormat
should be used.
*
* @type {VertexFormat}
* @constant
*
* @see VertexFormat#position
* @see VertexFormat#normal
*/
VertexFormat.DEFAULT = VertexFormat.POSITION_NORMAL_AND_ST;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
VertexFormat.packedLength = 6;
/**
* Stores the provided instance into the provided array.
*
* @param {VertexFormat} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
VertexFormat.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.position ? 1.0 : 0.0;
array[startingIndex++] = value.normal ? 1.0 : 0.0;
array[startingIndex++] = value.st ? 1.0 : 0.0;
array[startingIndex++] = value.binormal ? 1.0 : 0.0;
array[startingIndex++] = value.tangent ? 1.0 : 0.0;
array[startingIndex++] = value.color ? 1.0 : 0.0;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {VertexFormat} [result] The object into which to store the result.
* @returns {VertexFormat} The modified result parameter or a new VertexFormat instance if one was not provided.
*/
VertexFormat.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new VertexFormat();
}
result.position = array[startingIndex++] === 1.0;
result.normal = array[startingIndex++] === 1.0;
result.st = array[startingIndex++] === 1.0;
result.binormal = array[startingIndex++] === 1.0;
result.tangent = array[startingIndex++] === 1.0;
result.color = array[startingIndex++] === 1.0;
return result;
};
/**
* Duplicates a VertexFormat instance.
*
* @param {VertexFormat} cartesian The vertex format to duplicate.
* @param {VertexFormat} [result] The object onto which to store the result.
* @returns {VertexFormat} The modified result parameter or a new VertexFormat instance if one was not provided. (Returns undefined if vertexFormat is undefined)
*/
VertexFormat.clone = function(vertexFormat, result) {
if (!defined(vertexFormat)) {
return undefined;
}
if (!defined(result)) {
result = new VertexFormat();
}
result.position = vertexFormat.position;
result.normal = vertexFormat.normal;
result.st = vertexFormat.st;
result.binormal = vertexFormat.binormal;
result.tangent = vertexFormat.tangent;
result.color = vertexFormat.color;
return result;
};
return VertexFormat;
});
/*global define*/
define('Core/BoxGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./PrimitiveType',
'./VertexFormat'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Geometry,
GeometryAttribute,
GeometryAttributes,
PrimitiveType,
VertexFormat) {
'use strict';
var diffScratch = new Cartesian3();
/**
* Describes a cube centered at the origin.
*
* @alias BoxGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box.
* @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
*
* @see BoxGeometry.fromDimensions
* @see BoxGeometry.createGeometry
* @see Packable
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo}
*
* @example
* var box = new Cesium.BoxGeometry({
* vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
* maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0),
* minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0)
* });
* var geometry = Cesium.BoxGeometry.createGeometry(box);
*/
function BoxGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var min = options.minimum;
var max = options.maximum;
if (!defined(min)) {
throw new DeveloperError('options.minimum is required.');
}
if (!defined(max)) {
throw new DeveloperError('options.maximum is required');
}
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
this._minimum = Cartesian3.clone(min);
this._maximum = Cartesian3.clone(max);
this._vertexFormat = vertexFormat;
this._workerName = 'createBoxGeometry';
}
/**
* Creates a cube centered at the origin given its dimensions.
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the Cartesian3
, respectively.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @returns {BoxGeometry}
*
* @exception {DeveloperError} All dimensions components must be greater than or equal to zero.
*
*
* @example
* var box = Cesium.BoxGeometry.fromDimensions({
* vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
* dimensions : new Cesium.Cartesian3(500000.0, 500000.0, 500000.0)
* });
* var geometry = Cesium.BoxGeometry.createGeometry(box);
*
* @see BoxGeometry.createGeometry
*/
BoxGeometry.fromDimensions = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var dimensions = options.dimensions;
if (!defined(dimensions)) {
throw new DeveloperError('options.dimensions is required.');
}
if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) {
throw new DeveloperError('All dimensions components must be greater than or equal to zero.');
}
var corner = Cartesian3.multiplyByScalar(dimensions, 0.5, new Cartesian3());
return new BoxGeometry({
minimum : Cartesian3.negate(corner, new Cartesian3()),
maximum : corner,
vertexFormat : options.vertexFormat
});
};
/**
* Creates a cube from the dimensions of an AxisAlignedBoundingBox.
*
* @param {AxisAlignedBoundingBox} boundingBox A description of the AxisAlignedBoundingBox.
* @returns {BoxGeometry}
*
*
*
* @example
* var aabb = Cesium.AxisAlignedBoundingBox.fromPoints(Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ]));
* var box = Cesium.BoxGeometry.fromAxisAlignedBoundingBox(aabb);
*
* @see BoxGeometry.createGeometry
*/
BoxGeometry.fromAxisAlignedBoundingBox = function (boundingBox) {
if (!defined(boundingBox)) {
throw new DeveloperError('boundingBox is required.');
}
return new BoxGeometry({
minimum : boundingBox.minimum,
maximum : boundingBox.maximum
});
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
BoxGeometry.packedLength = 2 * Cartesian3.packedLength + VertexFormat.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {BoxGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
BoxGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._minimum, array, startingIndex);
Cartesian3.pack(value._maximum, array, startingIndex + Cartesian3.packedLength);
VertexFormat.pack(value._vertexFormat, array, startingIndex + 2 * Cartesian3.packedLength);
return array;
};
var scratchMin = new Cartesian3();
var scratchMax = new Cartesian3();
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
minimum: scratchMin,
maximum: scratchMax,
vertexFormat: scratchVertexFormat
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {BoxGeometry} [result] The object into which to store the result.
* @returns {BoxGeometry} The modified result parameter or a new BoxGeometry instance if one was not provided.
*/
BoxGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var min = Cartesian3.unpack(array, startingIndex, scratchMin);
var max = Cartesian3.unpack(array, startingIndex + Cartesian3.packedLength, scratchMax);
var vertexFormat = VertexFormat.unpack(array, startingIndex + 2 * Cartesian3.packedLength, scratchVertexFormat);
if (!defined(result)) {
return new BoxGeometry(scratchOptions);
}
result._minimum = Cartesian3.clone(min, result._minimum);
result._maximum = Cartesian3.clone(max, result._maximum);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
return result;
};
/**
* Computes the geometric representation of a box, including its vertices, indices, and a bounding sphere.
*
* @param {BoxGeometry} boxGeometry A description of the box.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
BoxGeometry.createGeometry = function(boxGeometry) {
var min = boxGeometry._minimum;
var max = boxGeometry._maximum;
var vertexFormat = boxGeometry._vertexFormat;
if (Cartesian3.equals(min, max)) {
return;
}
var attributes = new GeometryAttributes();
var indices;
var positions;
if (vertexFormat.position &&
(vertexFormat.st || vertexFormat.normal || vertexFormat.binormal || vertexFormat.tangent)) {
if (vertexFormat.position) {
// 8 corner points. Duplicated 3 times each for each incident edge/face.
positions = new Float64Array(6 * 4 * 3);
// +z face
positions[0] = min.x;
positions[1] = min.y;
positions[2] = max.z;
positions[3] = max.x;
positions[4] = min.y;
positions[5] = max.z;
positions[6] = max.x;
positions[7] = max.y;
positions[8] = max.z;
positions[9] = min.x;
positions[10] = max.y;
positions[11] = max.z;
// -z face
positions[12] = min.x;
positions[13] = min.y;
positions[14] = min.z;
positions[15] = max.x;
positions[16] = min.y;
positions[17] = min.z;
positions[18] = max.x;
positions[19] = max.y;
positions[20] = min.z;
positions[21] = min.x;
positions[22] = max.y;
positions[23] = min.z;
// +x face
positions[24] = max.x;
positions[25] = min.y;
positions[26] = min.z;
positions[27] = max.x;
positions[28] = max.y;
positions[29] = min.z;
positions[30] = max.x;
positions[31] = max.y;
positions[32] = max.z;
positions[33] = max.x;
positions[34] = min.y;
positions[35] = max.z;
// -x face
positions[36] = min.x;
positions[37] = min.y;
positions[38] = min.z;
positions[39] = min.x;
positions[40] = max.y;
positions[41] = min.z;
positions[42] = min.x;
positions[43] = max.y;
positions[44] = max.z;
positions[45] = min.x;
positions[46] = min.y;
positions[47] = max.z;
// +y face
positions[48] = min.x;
positions[49] = max.y;
positions[50] = min.z;
positions[51] = max.x;
positions[52] = max.y;
positions[53] = min.z;
positions[54] = max.x;
positions[55] = max.y;
positions[56] = max.z;
positions[57] = min.x;
positions[58] = max.y;
positions[59] = max.z;
// -y face
positions[60] = min.x;
positions[61] = min.y;
positions[62] = min.z;
positions[63] = max.x;
positions[64] = min.y;
positions[65] = min.z;
positions[66] = max.x;
positions[67] = min.y;
positions[68] = max.z;
positions[69] = min.x;
positions[70] = min.y;
positions[71] = max.z;
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
}
if (vertexFormat.normal) {
var normals = new Float32Array(6 * 4 * 3);
// +z face
normals[0] = 0.0;
normals[1] = 0.0;
normals[2] = 1.0;
normals[3] = 0.0;
normals[4] = 0.0;
normals[5] = 1.0;
normals[6] = 0.0;
normals[7] = 0.0;
normals[8] = 1.0;
normals[9] = 0.0;
normals[10] = 0.0;
normals[11] = 1.0;
// -z face
normals[12] = 0.0;
normals[13] = 0.0;
normals[14] = -1.0;
normals[15] = 0.0;
normals[16] = 0.0;
normals[17] = -1.0;
normals[18] = 0.0;
normals[19] = 0.0;
normals[20] = -1.0;
normals[21] = 0.0;
normals[22] = 0.0;
normals[23] = -1.0;
// +x face
normals[24] = 1.0;
normals[25] = 0.0;
normals[26] = 0.0;
normals[27] = 1.0;
normals[28] = 0.0;
normals[29] = 0.0;
normals[30] = 1.0;
normals[31] = 0.0;
normals[32] = 0.0;
normals[33] = 1.0;
normals[34] = 0.0;
normals[35] = 0.0;
// -x face
normals[36] = -1.0;
normals[37] = 0.0;
normals[38] = 0.0;
normals[39] = -1.0;
normals[40] = 0.0;
normals[41] = 0.0;
normals[42] = -1.0;
normals[43] = 0.0;
normals[44] = 0.0;
normals[45] = -1.0;
normals[46] = 0.0;
normals[47] = 0.0;
// +y face
normals[48] = 0.0;
normals[49] = 1.0;
normals[50] = 0.0;
normals[51] = 0.0;
normals[52] = 1.0;
normals[53] = 0.0;
normals[54] = 0.0;
normals[55] = 1.0;
normals[56] = 0.0;
normals[57] = 0.0;
normals[58] = 1.0;
normals[59] = 0.0;
// -y face
normals[60] = 0.0;
normals[61] = -1.0;
normals[62] = 0.0;
normals[63] = 0.0;
normals[64] = -1.0;
normals[65] = 0.0;
normals[66] = 0.0;
normals[67] = -1.0;
normals[68] = 0.0;
normals[69] = 0.0;
normals[70] = -1.0;
normals[71] = 0.0;
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.st) {
var texCoords = new Float32Array(6 * 4 * 2);
// +z face
texCoords[0] = 0.0;
texCoords[1] = 0.0;
texCoords[2] = 1.0;
texCoords[3] = 0.0;
texCoords[4] = 1.0;
texCoords[5] = 1.0;
texCoords[6] = 0.0;
texCoords[7] = 1.0;
// -z face
texCoords[8] = 1.0;
texCoords[9] = 0.0;
texCoords[10] = 0.0;
texCoords[11] = 0.0;
texCoords[12] = 0.0;
texCoords[13] = 1.0;
texCoords[14] = 1.0;
texCoords[15] = 1.0;
//+x face
texCoords[16] = 0.0;
texCoords[17] = 0.0;
texCoords[18] = 1.0;
texCoords[19] = 0.0;
texCoords[20] = 1.0;
texCoords[21] = 1.0;
texCoords[22] = 0.0;
texCoords[23] = 1.0;
// -x face
texCoords[24] = 1.0;
texCoords[25] = 0.0;
texCoords[26] = 0.0;
texCoords[27] = 0.0;
texCoords[28] = 0.0;
texCoords[29] = 1.0;
texCoords[30] = 1.0;
texCoords[31] = 1.0;
// +y face
texCoords[32] = 1.0;
texCoords[33] = 0.0;
texCoords[34] = 0.0;
texCoords[35] = 0.0;
texCoords[36] = 0.0;
texCoords[37] = 1.0;
texCoords[38] = 1.0;
texCoords[39] = 1.0;
// -y face
texCoords[40] = 0.0;
texCoords[41] = 0.0;
texCoords[42] = 1.0;
texCoords[43] = 0.0;
texCoords[44] = 1.0;
texCoords[45] = 1.0;
texCoords[46] = 0.0;
texCoords[47] = 1.0;
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : texCoords
});
}
if (vertexFormat.tangent) {
var tangents = new Float32Array(6 * 4 * 3);
// +z face
tangents[0] = 1.0;
tangents[1] = 0.0;
tangents[2] = 0.0;
tangents[3] = 1.0;
tangents[4] = 0.0;
tangents[5] = 0.0;
tangents[6] = 1.0;
tangents[7] = 0.0;
tangents[8] = 0.0;
tangents[9] = 1.0;
tangents[10] = 0.0;
tangents[11] = 0.0;
// -z face
tangents[12] = -1.0;
tangents[13] = 0.0;
tangents[14] = 0.0;
tangents[15] = -1.0;
tangents[16] = 0.0;
tangents[17] = 0.0;
tangents[18] = -1.0;
tangents[19] = 0.0;
tangents[20] = 0.0;
tangents[21] = -1.0;
tangents[22] = 0.0;
tangents[23] = 0.0;
// +x face
tangents[24] = 0.0;
tangents[25] = 1.0;
tangents[26] = 0.0;
tangents[27] = 0.0;
tangents[28] = 1.0;
tangents[29] = 0.0;
tangents[30] = 0.0;
tangents[31] = 1.0;
tangents[32] = 0.0;
tangents[33] = 0.0;
tangents[34] = 1.0;
tangents[35] = 0.0;
// -x face
tangents[36] = 0.0;
tangents[37] = -1.0;
tangents[38] = 0.0;
tangents[39] = 0.0;
tangents[40] = -1.0;
tangents[41] = 0.0;
tangents[42] = 0.0;
tangents[43] = -1.0;
tangents[44] = 0.0;
tangents[45] = 0.0;
tangents[46] = -1.0;
tangents[47] = 0.0;
// +y face
tangents[48] = -1.0;
tangents[49] = 0.0;
tangents[50] = 0.0;
tangents[51] = -1.0;
tangents[52] = 0.0;
tangents[53] = 0.0;
tangents[54] = -1.0;
tangents[55] = 0.0;
tangents[56] = 0.0;
tangents[57] = -1.0;
tangents[58] = 0.0;
tangents[59] = 0.0;
// -y face
tangents[60] = 1.0;
tangents[61] = 0.0;
tangents[62] = 0.0;
tangents[63] = 1.0;
tangents[64] = 0.0;
tangents[65] = 0.0;
tangents[66] = 1.0;
tangents[67] = 0.0;
tangents[68] = 0.0;
tangents[69] = 1.0;
tangents[70] = 0.0;
tangents[71] = 0.0;
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
var binormals = new Float32Array(6 * 4 * 3);
// +z face
binormals[0] = 0.0;
binormals[1] = 1.0;
binormals[2] = 0.0;
binormals[3] = 0.0;
binormals[4] = 1.0;
binormals[5] = 0.0;
binormals[6] = 0.0;
binormals[7] = 1.0;
binormals[8] = 0.0;
binormals[9] = 0.0;
binormals[10] = 1.0;
binormals[11] = 0.0;
// -z face
binormals[12] = 0.0;
binormals[13] = 1.0;
binormals[14] = 0.0;
binormals[15] = 0.0;
binormals[16] = 1.0;
binormals[17] = 0.0;
binormals[18] = 0.0;
binormals[19] = 1.0;
binormals[20] = 0.0;
binormals[21] = 0.0;
binormals[22] = 1.0;
binormals[23] = 0.0;
// +x face
binormals[24] = 0.0;
binormals[25] = 0.0;
binormals[26] = 1.0;
binormals[27] = 0.0;
binormals[28] = 0.0;
binormals[29] = 1.0;
binormals[30] = 0.0;
binormals[31] = 0.0;
binormals[32] = 1.0;
binormals[33] = 0.0;
binormals[34] = 0.0;
binormals[35] = 1.0;
// -x face
binormals[36] = 0.0;
binormals[37] = 0.0;
binormals[38] = 1.0;
binormals[39] = 0.0;
binormals[40] = 0.0;
binormals[41] = 1.0;
binormals[42] = 0.0;
binormals[43] = 0.0;
binormals[44] = 1.0;
binormals[45] = 0.0;
binormals[46] = 0.0;
binormals[47] = 1.0;
// +y face
binormals[48] = 0.0;
binormals[49] = 0.0;
binormals[50] = 1.0;
binormals[51] = 0.0;
binormals[52] = 0.0;
binormals[53] = 1.0;
binormals[54] = 0.0;
binormals[55] = 0.0;
binormals[56] = 1.0;
binormals[57] = 0.0;
binormals[58] = 0.0;
binormals[59] = 1.0;
// -y face
binormals[60] = 0.0;
binormals[61] = 0.0;
binormals[62] = 1.0;
binormals[63] = 0.0;
binormals[64] = 0.0;
binormals[65] = 1.0;
binormals[66] = 0.0;
binormals[67] = 0.0;
binormals[68] = 1.0;
binormals[69] = 0.0;
binormals[70] = 0.0;
binormals[71] = 1.0;
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
// 12 triangles: 6 faces, 2 triangles each.
indices = new Uint16Array(6 * 2 * 3);
// +z face
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 0;
indices[4] = 2;
indices[5] = 3;
// -z face
indices[6] = 4 + 2;
indices[7] = 4 + 1;
indices[8] = 4 + 0;
indices[9] = 4 + 3;
indices[10] = 4 + 2;
indices[11] = 4 + 0;
// +x face
indices[12] = 8 + 0;
indices[13] = 8 + 1;
indices[14] = 8 + 2;
indices[15] = 8 + 0;
indices[16] = 8 + 2;
indices[17] = 8 + 3;
// -x face
indices[18] = 12 + 2;
indices[19] = 12 + 1;
indices[20] = 12 + 0;
indices[21] = 12 + 3;
indices[22] = 12 + 2;
indices[23] = 12 + 0;
// +y face
indices[24] = 16 + 2;
indices[25] = 16 + 1;
indices[26] = 16 + 0;
indices[27] = 16 + 3;
indices[28] = 16 + 2;
indices[29] = 16 + 0;
// -y face
indices[30] = 20 + 0;
indices[31] = 20 + 1;
indices[32] = 20 + 2;
indices[33] = 20 + 0;
indices[34] = 20 + 2;
indices[35] = 20 + 3;
} else {
// Positions only - no need to duplicate corner points
positions = new Float64Array(8 * 3);
positions[0] = min.x;
positions[1] = min.y;
positions[2] = min.z;
positions[3] = max.x;
positions[4] = min.y;
positions[5] = min.z;
positions[6] = max.x;
positions[7] = max.y;
positions[8] = min.z;
positions[9] = min.x;
positions[10] = max.y;
positions[11] = min.z;
positions[12] = min.x;
positions[13] = min.y;
positions[14] = max.z;
positions[15] = max.x;
positions[16] = min.y;
positions[17] = max.z;
positions[18] = max.x;
positions[19] = max.y;
positions[20] = max.z;
positions[21] = min.x;
positions[22] = max.y;
positions[23] = max.z;
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
// 12 triangles: 6 faces, 2 triangles each.
indices = new Uint16Array(6 * 2 * 3);
// plane z = corner.Z
indices[0] = 4;
indices[1] = 5;
indices[2] = 6;
indices[3] = 4;
indices[4] = 6;
indices[5] = 7;
// plane z = -corner.Z
indices[6] = 1;
indices[7] = 0;
indices[8] = 3;
indices[9] = 1;
indices[10] = 3;
indices[11] = 2;
// plane x = corner.X
indices[12] = 1;
indices[13] = 6;
indices[14] = 5;
indices[15] = 1;
indices[16] = 2;
indices[17] = 6;
// plane y = corner.Y
indices[18] = 2;
indices[19] = 3;
indices[20] = 7;
indices[21] = 2;
indices[22] = 7;
indices[23] = 6;
// plane x = -corner.X
indices[24] = 3;
indices[25] = 0;
indices[26] = 4;
indices[27] = 3;
indices[28] = 4;
indices[29] = 7;
// plane y = -corner.Y
indices[30] = 0;
indices[31] = 1;
indices[32] = 5;
indices[33] = 0;
indices[34] = 5;
indices[35] = 4;
}
var diff = Cartesian3.subtract(max, min, diffScratch);
var radius = Cartesian3.magnitude(diff) * 0.5;
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : new BoundingSphere(Cartesian3.ZERO, radius)
});
};
return BoxGeometry;
});
/*global define*/
define('Core/BoxOutlineGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./PrimitiveType'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Geometry,
GeometryAttribute,
GeometryAttributes,
PrimitiveType) {
'use strict';
var diffScratch = new Cartesian3();
/**
* A description of the outline of a cube centered at the origin.
*
* @alias BoxOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box.
* @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box.
*
* @see BoxOutlineGeometry.fromDimensions
* @see BoxOutlineGeometry.createGeometry
* @see Packable
*
* @example
* var box = new Cesium.BoxOutlineGeometry({
* maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0),
* minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0)
* });
* var geometry = Cesium.BoxOutlineGeometry.createGeometry(box);
*/
function BoxOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var min = options.minimum;
var max = options.maximum;
if (!defined(min)) {
throw new DeveloperError('options.minimum is required.');
}
if (!defined(max)) {
throw new DeveloperError('options.maximum is required');
}
this._min = Cartesian3.clone(min);
this._max = Cartesian3.clone(max);
this._workerName = 'createBoxOutlineGeometry';
}
/**
* Creates an outline of a cube centered at the origin given its dimensions.
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the Cartesian3
, respectively.
* @returns {BoxOutlineGeometry}
*
* @exception {DeveloperError} All dimensions components must be greater than or equal to zero.
*
*
* @example
* var box = Cesium.BoxOutlineGeometry.fromDimensions({
* dimensions : new Cesium.Cartesian3(500000.0, 500000.0, 500000.0)
* });
* var geometry = Cesium.BoxOutlineGeometry.createGeometry(box);
*
* @see BoxOutlineGeometry.createGeometry
*/
BoxOutlineGeometry.fromDimensions = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var dimensions = options.dimensions;
if (!defined(dimensions)) {
throw new DeveloperError('options.dimensions is required.');
}
if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) {
throw new DeveloperError('All dimensions components must be greater than or equal to zero.');
}
var corner = Cartesian3.multiplyByScalar(dimensions, 0.5, new Cartesian3());
return new BoxOutlineGeometry({
minimum : Cartesian3.negate(corner, new Cartesian3()),
maximum : corner
});
};
/**
* Creates an outline of a cube from the dimensions of an AxisAlignedBoundingBox.
*
* @param {AxisAlignedBoundingBox} boundingBox A description of the AxisAlignedBoundingBox.
* @returns {BoxOutlineGeometry}
*
*
*
* @example
* var aabb = Cesium.AxisAlignedBoundingBox.fromPoints(Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ]));
* var box = Cesium.BoxOutlineGeometry.fromAxisAlignedBoundingBox(aabb);
*
* @see BoxOutlineGeometry.createGeometry
*/
BoxOutlineGeometry.fromAxisAlignedBoundingBox = function(boundingBox) {
if (!defined(boundingBox)) {
throw new DeveloperError('boundingBox is required.');
}
return new BoxOutlineGeometry({
minimum : boundingBox.minimum,
maximum : boundingBox.maximum
});
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
BoxOutlineGeometry.packedLength = 2 * Cartesian3.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {BoxOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
BoxOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._min, array, startingIndex);
Cartesian3.pack(value._max, array, startingIndex + Cartesian3.packedLength);
return array;
};
var scratchMin = new Cartesian3();
var scratchMax = new Cartesian3();
var scratchOptions = {
minimum : scratchMin,
maximum : scratchMax
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {BoxOutlineGeometry} [result] The object into which to store the result.
* @returns {BoxOutlineGeometry} The modified result parameter or a new BoxOutlineGeometry instance if one was not provided.
*/
BoxOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var min = Cartesian3.unpack(array, startingIndex, scratchMin);
var max = Cartesian3.unpack(array, startingIndex + Cartesian3.packedLength, scratchMax);
if (!defined(result)) {
return new BoxOutlineGeometry(scratchOptions);
}
result._min = Cartesian3.clone(min, result._min);
result._max = Cartesian3.clone(max, result._max);
return result;
};
/**
* Computes the geometric representation of an outline of a box, including its vertices, indices, and a bounding sphere.
*
* @param {BoxOutlineGeometry} boxGeometry A description of the box outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
BoxOutlineGeometry.createGeometry = function(boxGeometry) {
var min = boxGeometry._min;
var max = boxGeometry._max;
if (Cartesian3.equals(min, max)) {
return;
}
var attributes = new GeometryAttributes();
var indices = new Uint16Array(12 * 2);
var positions = new Float64Array(8 * 3);
positions[0] = min.x;
positions[1] = min.y;
positions[2] = min.z;
positions[3] = max.x;
positions[4] = min.y;
positions[5] = min.z;
positions[6] = max.x;
positions[7] = max.y;
positions[8] = min.z;
positions[9] = min.x;
positions[10] = max.y;
positions[11] = min.z;
positions[12] = min.x;
positions[13] = min.y;
positions[14] = max.z;
positions[15] = max.x;
positions[16] = min.y;
positions[17] = max.z;
positions[18] = max.x;
positions[19] = max.y;
positions[20] = max.z;
positions[21] = min.x;
positions[22] = max.y;
positions[23] = max.z;
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
// top
indices[0] = 4;
indices[1] = 5;
indices[2] = 5;
indices[3] = 6;
indices[4] = 6;
indices[5] = 7;
indices[6] = 7;
indices[7] = 4;
// bottom
indices[8] = 0;
indices[9] = 1;
indices[10] = 1;
indices[11] = 2;
indices[12] = 2;
indices[13] = 3;
indices[14] = 3;
indices[15] = 0;
// left
indices[16] = 0;
indices[17] = 4;
indices[18] = 1;
indices[19] = 5;
//right
indices[20] = 2;
indices[21] = 6;
indices[22] = 3;
indices[23] = 7;
var diff = Cartesian3.subtract(max, min, diffScratch);
var radius = Cartesian3.magnitude(diff) * 0.5;
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : new BoundingSphere(Cartesian3.ZERO, radius)
});
};
return BoxOutlineGeometry;
});
/*global define*/
define('Core/cancelAnimationFrame',[
'./defined'
], function(
defined) {
'use strict';
if (typeof window === 'undefined') {
return;
}
var implementation = window.cancelAnimationFrame;
(function() {
// look for vendor prefixed function
if (!defined(implementation)) {
var vendors = ['webkit', 'moz', 'ms', 'o'];
var i = 0;
var len = vendors.length;
while (i < len && !defined(implementation)) {
implementation = window[vendors[i] + 'CancelAnimationFrame'];
if (!defined(implementation)) {
implementation = window[vendors[i] + 'CancelRequestAnimationFrame'];
}
++i;
}
}
// otherwise, assume requestAnimationFrame is based on setTimeout, so use clearTimeout
if (!defined(implementation)) {
implementation = clearTimeout;
}
})();
/**
* A browser-independent function to cancel an animation frame requested using {@link requestAnimationFrame}.
*
* @exports cancelAnimationFrame
*
* @param {Number} requestID The value returned by {@link requestAnimationFrame}.
*
* @see {@link http://www.w3.org/TR/animation-timing/#the-WindowAnimationTiming-interface|The WindowAnimationTiming interface}
*/
function cancelAnimationFrame(requestID) {
// we need this extra wrapper function because the native cancelAnimationFrame
// functions must be invoked on the global scope (window), which is not the case
// if invoked as Cesium.cancelAnimationFrame(requestID)
implementation(requestID);
}
return cancelAnimationFrame;
});
/*global define*/
define('Core/Spline',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Creates a curve parameterized and evaluated by time. This type describes an interface
* and is not intended to be instantiated directly.
*
* @alias Spline
* @constructor
*
* @see CatmullRomSpline
* @see HermiteSpline
* @see LinearSpline
* @see QuaternionSpline
*/
function Spline() {
/**
* An array of times for the control points.
* @type {Number[]}
* @default undefined
*/
this.times = undefined;
/**
* An array of control points.
* @type {Cartesian3[]|Quaternion[]}
* @default undefined
*/
this.points = undefined;
DeveloperError.throwInstantiationError();
}
/**
* Evaluates the curve at a given time.
* @function
*
* @param {Number} time The time at which to evaluate the curve.
* @param {Cartesian3|Quaternion} [result] The object onto which to store the result.
* @returns {Cartesian3|Quaternion} The modified result parameter or a new instance of the point on the curve at the given time.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
Spline.prototype.evaluate = DeveloperError.throwInstantiationError;
/**
* Finds an index i
in times
such that the parameter
* time
is in the interval [times[i], times[i + 1]]
.
*
* @param {Number} time The time.
* @param {Number} startIndex The index from which to start the search.
* @returns {Number} The index for the element at the start of the interval.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
Spline.prototype.findTimeInterval = function(time, startIndex) {
var times = this.times;
var length = times.length;
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (time < times[0] || time > times[length - 1]) {
throw new DeveloperError('time is out of range.');
}
// Take advantage of temporal coherence by checking current, next and previous intervals
// for containment of time.
startIndex = defaultValue(startIndex, 0);
if (time >= times[startIndex]) {
if (startIndex + 1 < length && time < times[startIndex + 1]) {
return startIndex;
} else if (startIndex + 2 < length && time < times[startIndex + 2]) {
return startIndex + 1;
}
} else if (startIndex - 1 >= 0 && time >= times[startIndex - 1]) {
return startIndex - 1;
}
// The above failed so do a linear search. For the use cases so far, the
// length of the list is less than 10. In the future, if there is a bottle neck,
// it might be here.
var i;
if (time > times[startIndex]) {
for (i = startIndex; i < length - 1; ++i) {
if (time >= times[i] && time < times[i + 1]) {
break;
}
}
} else {
for (i = startIndex - 1; i >= 0; --i) {
if (time >= times[i] && time < times[i + 1]) {
break;
}
}
}
if (i === length - 1) {
i = length - 2;
}
return i;
};
return Spline;
});
/*global define*/
define('Core/LinearSpline',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Spline'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Spline) {
'use strict';
/**
* A spline that uses piecewise linear interpolation to create a curve.
*
* @alias LinearSpline
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
* The values are in no way connected to the clock time. They are the parameterization for the curve.
* @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
*
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times.length must be equal to points.length.
*
*
* @example
* var times = [ 0.0, 1.5, 3.0, 4.5, 6.0 ];
* var spline = new Cesium.LinearSpline({
* times : times,
* points : [
* new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
* new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
* new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
* new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
* new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
* ]
* });
*
* var p0 = spline.evaluate(times[0]);
*
* @see HermiteSpline
* @see CatmullRomSpline
* @see QuaternionSpline
*/
function LinearSpline(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var points = options.points;
var times = options.times;
if (!defined(points) || !defined(times)) {
throw new DeveloperError('points and times are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length) {
throw new DeveloperError('times.length must be equal to points.length.');
}
this._times = times;
this._points = points;
this._lastTimeIndex = 0;
}
defineProperties(LinearSpline.prototype, {
/**
* An array of times for the control points.
*
* @memberof LinearSpline.prototype
*
* @type {Number[]}
* @readonly
*/
times : {
get : function() {
return this._times;
}
},
/**
* An array of {@link Cartesian3} control points.
*
* @memberof LinearSpline.prototype
*
* @type {Cartesian3[]}
* @readonly
*/
points : {
get : function() {
return this._points;
}
}
});
/**
* Finds an index i
in times
such that the parameter
* time
is in the interval [times[i], times[i + 1]]
.
* @function
*
* @param {Number} time The time.
* @returns {Number} The index for the element at the start of the interval.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
LinearSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;
/**
* Evaluates the curve at a given time.
*
* @param {Number} time The time at which to evaluate the curve.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
LinearSpline.prototype.evaluate = function(time, result) {
var points = this.points;
var times = this.times;
var i = this._lastTimeIndex = this.findTimeInterval(time, this._lastTimeIndex);
var u = (time - times[i]) / (times[i + 1] - times[i]);
if (!defined(result)) {
result = new Cartesian3();
}
return Cartesian3.lerp(points[i], points[i + 1], u, result);
};
return LinearSpline;
});
/*global define*/
define('Core/TridiagonalSystemSolver',[
'./Cartesian3',
'./defined',
'./DeveloperError'
], function(
Cartesian3,
defined,
DeveloperError) {
'use strict';
/**
* Uses the Tridiagonal Matrix Algorithm, also known as the Thomas Algorithm, to solve
* a system of linear equations where the coefficient matrix is a tridiagonal matrix.
*
* @exports TridiagonalSystemSolver
*/
var TridiagonalSystemSolver = {};
/**
* Solves a tridiagonal system of linear equations.
*
* @param {Number[]} diagonal An array with length n
that contains the diagonal of the coefficient matrix.
* @param {Number[]} lower An array with length n - 1
that contains the lower diagonal of the coefficient matrix.
* @param {Number[]} upper An array with length n - 1
that contains the upper diagonal of the coefficient matrix.
* @param {Cartesian3[]} right An array of Cartesians with length n
that is the right side of the system of equations.
*
* @exception {DeveloperError} diagonal and right must have the same lengths.
* @exception {DeveloperError} lower and upper must have the same lengths.
* @exception {DeveloperError} lower and upper must be one less than the length of diagonal.
*
* @performance Linear time.
*
* @example
* var lowerDiagonal = [1.0, 1.0, 1.0, 1.0];
* var diagonal = [2.0, 4.0, 4.0, 4.0, 2.0];
* var upperDiagonal = [1.0, 1.0, 1.0, 1.0];
* var rightHandSide = [
* new Cesium.Cartesian3(410757.0, -1595711.0, 1375302.0),
* new Cesium.Cartesian3(-5986705.0, -2190640.0, 1099600.0),
* new Cesium.Cartesian3(-12593180.0, 288588.0, -1755549.0),
* new Cesium.Cartesian3(-5349898.0, 2457005.0, -2685438.0),
* new Cesium.Cartesian3(845820.0, 1573488.0, -1205591.0)
* ];
*
* var solution = Cesium.TridiagonalSystemSolver.solve(lowerDiagonal, diagonal, upperDiagonal, rightHandSide);
*
* @returns {Cartesian3[]} An array of Cartesians with length n
that is the solution to the tridiagonal system of equations.
*/
TridiagonalSystemSolver.solve = function(lower, diagonal, upper, right) {
if (!defined(lower) || !(lower instanceof Array)) {
throw new DeveloperError('The array lower is required.');
}
if (!defined(diagonal) || !(diagonal instanceof Array)) {
throw new DeveloperError('The array diagonal is required.');
}
if (!defined(upper) || !(upper instanceof Array)) {
throw new DeveloperError('The array upper is required.');
}
if (!defined(right) || !(right instanceof Array)) {
throw new DeveloperError('The array right is required.');
}
if (diagonal.length !== right.length) {
throw new DeveloperError('diagonal and right must have the same lengths.');
}
if (lower.length !== upper.length) {
throw new DeveloperError('lower and upper must have the same lengths.');
} else if (lower.length !== diagonal.length - 1) {
throw new DeveloperError('lower and upper must be one less than the length of diagonal.');
}
var c = new Array(upper.length);
var d = new Array(right.length);
var x = new Array(right.length);
var i;
for (i = 0; i < d.length; i++) {
d[i] = new Cartesian3();
x[i] = new Cartesian3();
}
c[0] = upper[0] / diagonal[0];
d[0] = Cartesian3.multiplyByScalar(right[0], 1.0 / diagonal[0], d[0]);
var scalar;
for (i = 1; i < c.length; ++i) {
scalar = 1.0 / (diagonal[i] - c[i - 1] * lower[i - 1]);
c[i] = upper[i] * scalar;
d[i] = Cartesian3.subtract(right[i], Cartesian3.multiplyByScalar(d[i - 1], lower[i - 1], d[i]), d[i]);
d[i] = Cartesian3.multiplyByScalar(d[i], scalar, d[i]);
}
scalar = 1.0 / (diagonal[i] - c[i - 1] * lower[i - 1]);
d[i] = Cartesian3.subtract(right[i], Cartesian3.multiplyByScalar(d[i - 1], lower[i - 1], d[i]), d[i]);
d[i] = Cartesian3.multiplyByScalar(d[i], scalar, d[i]);
x[x.length - 1] = d[d.length - 1];
for (i = x.length - 2; i >= 0; --i) {
x[i] = Cartesian3.subtract(d[i], Cartesian3.multiplyByScalar(x[i + 1], c[i], x[i]), x[i]);
}
return x;
};
return TridiagonalSystemSolver;
});
/*global define*/
define('Core/HermiteSpline',[
'./Cartesian3',
'./Cartesian4',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./LinearSpline',
'./Matrix4',
'./Spline',
'./TridiagonalSystemSolver'
], function(
Cartesian3,
Cartesian4,
defaultValue,
defined,
defineProperties,
DeveloperError,
LinearSpline,
Matrix4,
Spline,
TridiagonalSystemSolver) {
'use strict';
var scratchLower = [];
var scratchDiagonal = [];
var scratchUpper = [];
var scratchRight = [];
function generateClamped(points, firstTangent, lastTangent) {
var l = scratchLower;
var u = scratchUpper;
var d = scratchDiagonal;
var r = scratchRight;
l.length = u.length = points.length - 1;
d.length = r.length = points.length;
var i;
l[0] = d[0] = 1.0;
u[0] = 0.0;
var right = r[0];
if (!defined(right)) {
right = r[0] = new Cartesian3();
}
Cartesian3.clone(firstTangent, right);
for (i = 1; i < l.length - 1; ++i) {
l[i] = u[i] = 1.0;
d[i] = 4.0;
right = r[i];
if (!defined(right)) {
right = r[i] = new Cartesian3();
}
Cartesian3.subtract(points[i + 1], points[i - 1], right);
Cartesian3.multiplyByScalar(right, 3.0, right);
}
l[i] = 0.0;
u[i] = 1.0;
d[i] = 4.0;
right = r[i];
if (!defined(right)) {
right = r[i] = new Cartesian3();
}
Cartesian3.subtract(points[i + 1], points[i - 1], right);
Cartesian3.multiplyByScalar(right, 3.0, right);
d[i + 1] = 1.0;
right = r[i + 1];
if (!defined(right)) {
right = r[i + 1] = new Cartesian3();
}
Cartesian3.clone(lastTangent, right);
return TridiagonalSystemSolver.solve(l, d, u, r);
}
function generateNatural(points){
var l = scratchLower;
var u = scratchUpper;
var d = scratchDiagonal;
var r = scratchRight;
l.length = u.length = points.length - 1;
d.length = r.length = points.length;
var i;
l[0] = u[0] = 1.0;
d[0] = 2.0;
var right = r[0];
if (!defined(right)) {
right = r[0] = new Cartesian3();
}
Cartesian3.subtract(points[1], points[0], right);
Cartesian3.multiplyByScalar(right, 3.0, right);
for (i = 1; i < l.length; ++i) {
l[i] = u[i] = 1.0;
d[i] = 4.0;
right = r[i];
if (!defined(right)) {
right = r[i] = new Cartesian3();
}
Cartesian3.subtract(points[i + 1], points[i - 1], right);
Cartesian3.multiplyByScalar(right, 3.0, right);
}
d[i] = 2.0;
right = r[i];
if (!defined(right)) {
right = r[i] = new Cartesian3();
}
Cartesian3.subtract(points[i], points[i - 1], right);
Cartesian3.multiplyByScalar(right, 3.0, right);
return TridiagonalSystemSolver.solve(l, d, u, r);
}
/**
* A Hermite spline is a cubic interpolating spline. Points, incoming tangents, outgoing tangents, and times
* must be defined for each control point. The outgoing tangents are defined for points [0, n - 2] and the incoming
* tangents are defined for points [1, n - 1]. For example, when interpolating a segment of the curve between points[i]
and
* points[i + 1]
, the tangents at the points will be outTangents[i]
and inTangents[i]
,
* respectively.
*
* @alias HermiteSpline
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
* The values are in no way connected to the clock time. They are the parameterization for the curve.
* @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
* @param {Cartesian3[]} options.inTangents The array of {@link Cartesian3} incoming tangents at each control point.
* @param {Cartesian3[]} options.outTangents The array of {@link Cartesian3} outgoing tangents at each control point.
*
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times.length must be equal to points.length.
* @exception {DeveloperError} inTangents and outTangents must have a length equal to points.length - 1.
*
*
* @example
* // Create a G1 continuous Hermite spline
* var times = [ 0.0, 1.5, 3.0, 4.5, 6.0 ];
* var spline = new Cesium.HermiteSpline({
* times : times,
* points : [
* new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
* new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
* new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
* new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
* new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
* ],
* outTangents : [
* new Cesium.Cartesian3(1125196, -161816, 270551),
* new Cesium.Cartesian3(-996690.5, -365906.5, 184028.5),
* new Cesium.Cartesian3(-2096917, 48379.5, -292683.5),
* new Cesium.Cartesian3(-890902.5, 408999.5, -447115)
* ],
* inTangents : [
* new Cesium.Cartesian3(-1993381, -731813, 368057),
* new Cesium.Cartesian3(-4193834, 96759, -585367),
* new Cesium.Cartesian3(-1781805, 817999, -894230),
* new Cesium.Cartesian3(1165345, 112641, 47281)
* ]
* });
*
* var p0 = spline.evaluate(times[0]);
*
* @see CatmullRomSpline
* @see LinearSpline
* @see QuaternionSpline
*/
function HermiteSpline(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var points = options.points;
var times = options.times;
var inTangents = options.inTangents;
var outTangents = options.outTangents;
if (!defined(points) || !defined(times) || !defined(inTangents) || !defined(outTangents)) {
throw new DeveloperError('times, points, inTangents, and outTangents are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length) {
throw new DeveloperError('times.length must be equal to points.length.');
}
if (inTangents.length !== outTangents.length || inTangents.length !== points.length - 1) {
throw new DeveloperError('inTangents and outTangents must have a length equal to points.length - 1.');
}
this._times = times;
this._points = points;
this._inTangents = inTangents;
this._outTangents = outTangents;
this._lastTimeIndex = 0;
}
defineProperties(HermiteSpline.prototype, {
/**
* An array of times for the control points.
*
* @memberof HermiteSpline.prototype
*
* @type {Number[]}
* @readonly
*/
times : {
get : function() {
return this._times;
}
},
/**
* An array of {@link Cartesian3} control points.
*
* @memberof HermiteSpline.prototype
*
* @type {Cartesian3[]}
* @readonly
*/
points : {
get : function() {
return this._points;
}
},
/**
* An array of {@link Cartesian3} incoming tangents at each control point.
*
* @memberof HermiteSpline.prototype
*
* @type {Cartesian3[]}
* @readonly
*/
inTangents : {
get : function() {
return this._inTangents;
}
},
/**
* An array of {@link Cartesian3} outgoing tangents at each control point.
*
* @memberof HermiteSpline.prototype
*
* @type {Cartesian3[]}
* @readonly
*/
outTangents : {
get : function() {
return this._outTangents;
}
}
});
/**
* Creates a spline where the tangents at each control point are the same.
* The curves are guaranteed to be at least in the class C1 .
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times The array of control point times.
* @param {Cartesian3[]} options.points The array of control points.
* @param {Cartesian3[]} options.tangents The array of tangents at the control points.
* @returns {HermiteSpline} A hermite spline.
*
* @exception {DeveloperError} points, times and tangents are required.
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times, points and tangents must have the same length.
*
* @example
* var points = [
* new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
* new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
* new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
* new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
* new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
* ];
*
* // Add tangents
* var tangents = new Array(points.length);
* tangents[0] = new Cesium.Cartesian3(1125196, -161816, 270551);
* var temp = new Cesium.Cartesian3();
* for (var i = 1; i < tangents.length - 1; ++i) {
* tangents[i] = Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.subtract(points[i + 1], points[i - 1], temp), 0.5, new Cesium.Cartesian3());
* }
* tangents[tangents.length - 1] = new Cesium.Cartesian3(1165345, 112641, 47281);
*
* var spline = Cesium.HermiteSpline.createC1({
* times : times,
* points : points,
* tangents : tangents
* });
*/
HermiteSpline.createC1 = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var times = options.times;
var points = options.points;
var tangents = options.tangents;
if (!defined(points) || !defined(times) || !defined(tangents)) {
throw new DeveloperError('points, times and tangents are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length || times.length !== tangents.length) {
throw new DeveloperError('times, points and tangents must have the same length.');
}
var outTangents = tangents.slice(0, tangents.length - 1);
var inTangents = tangents.slice(1, tangents.length);
return new HermiteSpline({
times : times,
points : points,
inTangents : inTangents,
outTangents : outTangents
});
};
/**
* Creates a natural cubic spline. The tangents at the control points are generated
* to create a curve in the class C2 .
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times The array of control point times.
* @param {Cartesian3[]} options.points The array of control points.
* @returns {HermiteSpline|LinearSpline} A hermite spline or a linear spline if less than 3 control points were given.
*
* @exception {DeveloperError} points and times are required.
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times.length must be equal to points.length.
*
* @example
* // Create a natural cubic spline above the earth from Philadelphia to Los Angeles.
* var spline = Cesium.HermiteSpline.createNaturalCubic({
* times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
* points : [
* new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
* new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
* new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
* new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
* new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
* ]
* });
*/
HermiteSpline.createNaturalCubic = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var times = options.times;
var points = options.points;
if (!defined(points) || !defined(times)) {
throw new DeveloperError('points and times are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length) {
throw new DeveloperError('times.length must be equal to points.length.');
}
if (points.length < 3) {
return new LinearSpline({
points : points,
times : times
});
}
var tangents = generateNatural(points);
var outTangents = tangents.slice(0, tangents.length - 1);
var inTangents = tangents.slice(1, tangents.length);
return new HermiteSpline({
times : times,
points : points,
inTangents : inTangents,
outTangents : outTangents
});
};
/**
* Creates a clamped cubic spline. The tangents at the interior control points are generated
* to create a curve in the class C2 .
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times The array of control point times.
* @param {Cartesian3[]} options.points The array of control points.
* @param {Cartesian3} options.firstTangent The outgoing tangent of the first control point.
* @param {Cartesian3} options.lastTangent The incoming tangent of the last control point.
* @returns {HermiteSpline|LinearSpline} A hermite spline or a linear spline if less than 3 control points were given.
*
* @exception {DeveloperError} points, times, firstTangent and lastTangent are required.
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times.length must be equal to points.length.
*
* @example
* // Create a clamped cubic spline above the earth from Philadelphia to Los Angeles.
* var spline = Cesium.HermiteSpline.createClampedCubic({
* times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
* points : [
* new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
* new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
* new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
* new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
* new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
* ],
* firstTangent : new Cesium.Cartesian3(1125196, -161816, 270551),
* lastTangent : new Cesium.Cartesian3(1165345, 112641, 47281)
* });
*/
HermiteSpline.createClampedCubic = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var times = options.times;
var points = options.points;
var firstTangent = options.firstTangent;
var lastTangent = options.lastTangent;
if (!defined(points) || !defined(times) || !defined(firstTangent) || !defined(lastTangent)) {
throw new DeveloperError('points, times, firstTangent and lastTangent are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length) {
throw new DeveloperError('times.length must be equal to points.length.');
}
if (points.length < 3) {
return new LinearSpline({
points : points,
times : times
});
}
var tangents = generateClamped(points, firstTangent, lastTangent);
var outTangents = tangents.slice(0, tangents.length - 1);
var inTangents = tangents.slice(1, tangents.length);
return new HermiteSpline({
times : times,
points : points,
inTangents : inTangents,
outTangents : outTangents
});
};
HermiteSpline.hermiteCoefficientMatrix = new Matrix4(
2.0, -3.0, 0.0, 1.0,
-2.0, 3.0, 0.0, 0.0,
1.0, -2.0, 1.0, 0.0,
1.0, -1.0, 0.0, 0.0);
/**
* Finds an index i
in times
such that the parameter
* time
is in the interval [times[i], times[i + 1]]
.
* @function
*
* @param {Number} time The time.
* @returns {Number} The index for the element at the start of the interval.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
HermiteSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;
var scratchTimeVec = new Cartesian4();
var scratchTemp = new Cartesian3();
/**
* Evaluates the curve at a given time.
*
* @param {Number} time The time at which to evaluate the curve.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
HermiteSpline.prototype.evaluate = function(time, result) {
if (!defined(result)) {
result = new Cartesian3();
}
var points = this.points;
var times = this.times;
var inTangents = this.inTangents;
var outTangents = this.outTangents;
var i = this._lastTimeIndex = this.findTimeInterval(time, this._lastTimeIndex);
var u = (time - times[i]) / (times[i + 1] - times[i]);
var timeVec = scratchTimeVec;
timeVec.z = u;
timeVec.y = u * u;
timeVec.x = timeVec.y * u;
timeVec.w = 1.0;
var coefs = Matrix4.multiplyByVector(HermiteSpline.hermiteCoefficientMatrix, timeVec, timeVec);
result = Cartesian3.multiplyByScalar(points[i], coefs.x, result);
Cartesian3.multiplyByScalar(points[i + 1], coefs.y, scratchTemp);
Cartesian3.add(result, scratchTemp, result);
Cartesian3.multiplyByScalar(outTangents[i], coefs.z, scratchTemp);
Cartesian3.add(result, scratchTemp, result);
Cartesian3.multiplyByScalar(inTangents[i], coefs.w, scratchTemp);
return Cartesian3.add(result, scratchTemp, result);
};
return HermiteSpline;
});
/*global define*/
define('Core/CatmullRomSpline',[
'./Cartesian3',
'./Cartesian4',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./HermiteSpline',
'./Matrix4',
'./Spline'
], function(
Cartesian3,
Cartesian4,
defaultValue,
defined,
defineProperties,
DeveloperError,
HermiteSpline,
Matrix4,
Spline) {
'use strict';
var scratchTimeVec = new Cartesian4();
var scratchTemp0 = new Cartesian3();
var scratchTemp1 = new Cartesian3();
function createEvaluateFunction(spline) {
var points = spline.points;
var times = spline.times;
if (points.length < 3) {
var t0 = times[0];
var invSpan = 1.0 / (times[1] - t0);
var p0 = points[0];
var p1 = points[1];
return function(time, result) {
if (!defined(result)){
result = new Cartesian3();
}
var u = (time - t0) * invSpan;
return Cartesian3.lerp(p0, p1, u, result);
};
}
return function(time, result) {
if (!defined(result)) {
result = new Cartesian3();
}
var i = spline._lastTimeIndex = spline.findTimeInterval(time, spline._lastTimeIndex);
var u = (time - times[i]) / (times[i + 1] - times[i]);
var timeVec = scratchTimeVec;
timeVec.z = u;
timeVec.y = u * u;
timeVec.x = timeVec.y * u;
timeVec.w = 1.0;
var p0;
var p1;
var p2;
var p3;
var coefs;
if (i === 0) {
p0 = points[0];
p1 = points[1];
p2 = spline.firstTangent;
p3 = Cartesian3.subtract(points[2], p0, scratchTemp0);
Cartesian3.multiplyByScalar(p3, 0.5, p3);
coefs = Matrix4.multiplyByVector(HermiteSpline.hermiteCoefficientMatrix, timeVec, timeVec);
} else if (i === points.length - 2) {
p0 = points[i];
p1 = points[i + 1];
p3 = spline.lastTangent;
p2 = Cartesian3.subtract(p1, points[i - 1], scratchTemp0);
Cartesian3.multiplyByScalar(p2, 0.5, p2);
coefs = Matrix4.multiplyByVector(HermiteSpline.hermiteCoefficientMatrix, timeVec, timeVec);
} else {
p0 = points[i - 1];
p1 = points[i];
p2 = points[i + 1];
p3 = points[i + 2];
coefs = Matrix4.multiplyByVector(CatmullRomSpline.catmullRomCoefficientMatrix, timeVec, timeVec);
}
result = Cartesian3.multiplyByScalar(p0, coefs.x, result);
Cartesian3.multiplyByScalar(p1, coefs.y, scratchTemp1);
Cartesian3.add(result, scratchTemp1, result);
Cartesian3.multiplyByScalar(p2, coefs.z, scratchTemp1);
Cartesian3.add(result, scratchTemp1, result);
Cartesian3.multiplyByScalar(p3, coefs.w, scratchTemp1);
return Cartesian3.add(result, scratchTemp1, result);
};
}
var firstTangentScratch = new Cartesian3();
var lastTangentScratch = new Cartesian3();
/**
* A Catmull-Rom spline is a cubic spline where the tangent at control points,
* except the first and last, are computed using the previous and next control points.
* Catmull-Rom splines are in the class C1 .
*
* @alias CatmullRomSpline
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
* The values are in no way connected to the clock time. They are the parameterization for the curve.
* @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
* @param {Cartesian3} [options.firstTangent] The tangent of the curve at the first control point.
* If the tangent is not given, it will be estimated.
* @param {Cartesian3} [options.lastTangent] The tangent of the curve at the last control point.
* If the tangent is not given, it will be estimated.
*
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times.length must be equal to points.length.
*
*
* @example
* // spline above the earth from Philadelphia to Los Angeles
* var spline = new Cesium.CatmullRomSpline({
* times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
* points : [
* new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
* new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
* new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
* new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
* new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
* ]
* });
*
* var p0 = spline.evaluate(times[i]); // equal to positions[i]
* var p1 = spline.evaluate(times[i] + delta); // interpolated value when delta < times[i + 1] - times[i]
*
* @see HermiteSpline
* @see LinearSpline
* @see QuaternionSpline
*/
function CatmullRomSpline(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var points = options.points;
var times = options.times;
var firstTangent = options.firstTangent;
var lastTangent = options.lastTangent;
if (!defined(points) || !defined(times)) {
throw new DeveloperError('points and times are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length) {
throw new DeveloperError('times.length must be equal to points.length.');
}
if (points.length > 2) {
if (!defined(firstTangent)) {
firstTangent = firstTangentScratch;
Cartesian3.multiplyByScalar(points[1], 2.0, firstTangent);
Cartesian3.subtract(firstTangent, points[2], firstTangent);
Cartesian3.subtract(firstTangent, points[0], firstTangent);
Cartesian3.multiplyByScalar(firstTangent, 0.5, firstTangent);
}
if (!defined(lastTangent)) {
var n = points.length - 1;
lastTangent = lastTangentScratch;
Cartesian3.multiplyByScalar(points[n - 1], 2.0, lastTangent);
Cartesian3.subtract(points[n], lastTangent, lastTangent);
Cartesian3.add(lastTangent, points[n - 2], lastTangent);
Cartesian3.multiplyByScalar(lastTangent, 0.5, lastTangent);
}
}
this._times = times;
this._points = points;
this._firstTangent = Cartesian3.clone(firstTangent);
this._lastTangent = Cartesian3.clone(lastTangent);
this._evaluateFunction = createEvaluateFunction(this);
this._lastTimeIndex = 0;
}
defineProperties(CatmullRomSpline.prototype, {
/**
* An array of times for the control points.
*
* @memberof CatmullRomSpline.prototype
*
* @type {Number[]}
* @readonly
*/
times : {
get : function() {
return this._times;
}
},
/**
* An array of {@link Cartesian3} control points.
*
* @memberof CatmullRomSpline.prototype
*
* @type {Cartesian3[]}
* @readonly
*/
points : {
get : function() {
return this._points;
}
},
/**
* The tangent at the first control point.
*
* @memberof CatmullRomSpline.prototype
*
* @type {Cartesian3}
* @readonly
*/
firstTangent : {
get : function() {
return this._firstTangent;
}
},
/**
* The tangent at the last control point.
*
* @memberof CatmullRomSpline.prototype
*
* @type {Cartesian3}
* @readonly
*/
lastTangent : {
get : function() {
return this._lastTangent;
}
}
});
/**
* @private
*/
CatmullRomSpline.catmullRomCoefficientMatrix = new Matrix4(
-0.5, 1.0, -0.5, 0.0,
1.5, -2.5, 0.0, 1.0,
-1.5, 2.0, 0.5, 0.0,
0.5, -0.5, 0.0, 0.0);
/**
* Finds an index i
in times
such that the parameter
* time
is in the interval [times[i], times[i + 1]]
.
* @function
*
* @param {Number} time The time.
* @returns {Number} The index for the element at the start of the interval.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
CatmullRomSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;
/**
* Evaluates the curve at a given time.
*
* @param {Number} time The time at which to evaluate the curve.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
CatmullRomSpline.prototype.evaluate = function(time, result) {
return this._evaluateFunction(time, result);
};
return CatmullRomSpline;
});
/*global define*/
define('Core/IndexDatatype',[
'./defined',
'./DeveloperError',
'./freezeObject',
'./Math',
'./WebGLConstants'
], function(
defined,
DeveloperError,
freezeObject,
CesiumMath,
WebGLConstants) {
'use strict';
/**
* Constants for WebGL index datatypes. These corresponds to the
* type
parameter of {@link http://www.khronos.org/opengles/sdk/docs/man/xhtml/glDrawElements.xml|drawElements}.
*
* @exports IndexDatatype
*/
var IndexDatatype = {
/**
* 8-bit unsigned byte corresponding to UNSIGNED_BYTE
and the type
* of an element in Uint8Array
.
*
* @type {Number}
* @constant
*/
UNSIGNED_BYTE : WebGLConstants.UNSIGNED_BYTE,
/**
* 16-bit unsigned short corresponding to UNSIGNED_SHORT
and the type
* of an element in Uint16Array
.
*
* @type {Number}
* @constant
*/
UNSIGNED_SHORT : WebGLConstants.UNSIGNED_SHORT,
/**
* 32-bit unsigned int corresponding to UNSIGNED_INT
and the type
* of an element in Uint32Array
.
*
* @type {Number}
* @constant
*/
UNSIGNED_INT : WebGLConstants.UNSIGNED_INT
};
/**
* Returns the size, in bytes, of the corresponding datatype.
*
* @param {IndexDatatype} indexDatatype The index datatype to get the size of.
* @returns {Number} The size in bytes.
*
* @example
* // Returns 2
* var size = Cesium.IndexDatatype.getSizeInBytes(Cesium.IndexDatatype.UNSIGNED_SHORT);
*/
IndexDatatype.getSizeInBytes = function(indexDatatype) {
switch(indexDatatype) {
case IndexDatatype.UNSIGNED_BYTE:
return Uint8Array.BYTES_PER_ELEMENT;
case IndexDatatype.UNSIGNED_SHORT:
return Uint16Array.BYTES_PER_ELEMENT;
case IndexDatatype.UNSIGNED_INT:
return Uint32Array.BYTES_PER_ELEMENT;
}
throw new DeveloperError('indexDatatype is required and must be a valid IndexDatatype constant.');
};
/**
* Validates that the provided index datatype is a valid {@link IndexDatatype}.
*
* @param {IndexDatatype} indexDatatype The index datatype to validate.
* @returns {Boolean} true
if the provided index datatype is a valid value; otherwise, false
.
*
* @example
* if (!Cesium.IndexDatatype.validate(indexDatatype)) {
* throw new Cesium.DeveloperError('indexDatatype must be a valid value.');
* }
*/
IndexDatatype.validate = function(indexDatatype) {
return defined(indexDatatype) &&
(indexDatatype === IndexDatatype.UNSIGNED_BYTE ||
indexDatatype === IndexDatatype.UNSIGNED_SHORT ||
indexDatatype === IndexDatatype.UNSIGNED_INT);
};
/**
* Creates a typed array that will store indices, using either
* or Uint32Array
depending on the number of vertices.
*
* @param {Number} numberOfVertices Number of vertices that the indices will reference.
* @param {Any} indicesLengthOrArray Passed through to the typed array constructor.
* @returns {Uint16Array|Uint32Array} A Uint16Array
or Uint32Array
constructed with indicesLengthOrArray
.
*
* @example
* this.indices = Cesium.IndexDatatype.createTypedArray(positions.length / 3, numberOfIndices);
*/
IndexDatatype.createTypedArray = function(numberOfVertices, indicesLengthOrArray) {
if (!defined(numberOfVertices)) {
throw new DeveloperError('numberOfVertices is required.');
}
if (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
return new Uint32Array(indicesLengthOrArray);
}
return new Uint16Array(indicesLengthOrArray);
};
/**
* Creates a typed array from a source array buffer. The resulting typed array will store indices, using either
* or Uint32Array
depending on the number of vertices.
*
* @param {Number} numberOfVertices Number of vertices that the indices will reference.
* @param {ArrayBuffer} sourceArray Passed through to the typed array constructor.
* @param {Number} byteOffset Passed through to the typed array constructor.
* @param {Number} length Passed through to the typed array constructor.
* @returns {Uint16Array|Uint32Array} A Uint16Array
or Uint32Array
constructed with sourceArray
, byteOffset
, and length
.
*
*/
IndexDatatype.createTypedArrayFromArrayBuffer = function(numberOfVertices, sourceArray, byteOffset, length) {
if (!defined(numberOfVertices)) {
throw new DeveloperError('numberOfVertices is required.');
}
if (!defined(sourceArray)) {
throw new DeveloperError('sourceArray is required.');
}
if (!defined(byteOffset)) {
throw new DeveloperError('byteOffset is required.');
}
if (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
return new Uint32Array(sourceArray, byteOffset, length);
}
return new Uint16Array(sourceArray, byteOffset, length);
};
return freezeObject(IndexDatatype);
});
/*global define*/
define('Core/loadArrayBuffer',[
'./loadWithXhr'
], function(
loadWithXhr) {
'use strict';
/**
* Asynchronously loads the given URL as raw binary data. Returns a promise that will resolve to
* an ArrayBuffer once loaded, or reject if the URL failed to load. The data is loaded
* using XMLHttpRequest, which means that in order to make requests to another origin,
* the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
*
* @exports loadArrayBuffer
*
* @param {String|Promise.} url The URL of the binary data, or a promise for the URL.
* @param {Object} [headers] HTTP headers to send with the requests.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load a single URL asynchronously
* Cesium.loadArrayBuffer('some/url').then(function(arrayBuffer) {
* // use the data
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadArrayBuffer(url, headers) {
return loadWithXhr({
url : url,
responseType : 'arraybuffer',
headers : headers
});
}
return loadArrayBuffer;
});
/*global define*/
define('Core/Intersections2D',[
'./Cartesian3',
'./defined',
'./DeveloperError'
], function(
Cartesian3,
defined,
DeveloperError) {
'use strict';
/**
* Contains functions for operating on 2D triangles.
*
* @exports Intersections2D
*/
var Intersections2D = {};
/**
* Splits a 2D triangle at given axis-aligned threshold value and returns the resulting
* polygon on a given side of the threshold. The resulting polygon may have 0, 1, 2,
* 3, or 4 vertices.
*
* @param {Number} threshold The threshold coordinate value at which to clip the triangle.
* @param {Boolean} keepAbove true to keep the portion of the triangle above the threshold, or false
* to keep the portion below.
* @param {Number} u0 The coordinate of the first vertex in the triangle, in counter-clockwise order.
* @param {Number} u1 The coordinate of the second vertex in the triangle, in counter-clockwise order.
* @param {Number} u2 The coordinate of the third vertex in the triangle, in counter-clockwise order.
* @param {Number[]} [result] The array into which to copy the result. If this parameter is not supplied,
* a new array is constructed and returned.
* @returns {Number[]} The polygon that results after the clip, specified as a list of
* vertices. The vertices are specified in counter-clockwise order.
* Each vertex is either an index from the existing list (identified as
* a 0, 1, or 2) or -1 indicating a new vertex not in the original triangle.
* For new vertices, the -1 is followed by three additional numbers: the
* index of each of the two original vertices forming the line segment that
* the new vertex lies on, and the fraction of the distance from the first
* vertex to the second one.
*
* @example
* var result = Cesium.Intersections2D.clipTriangleAtAxisAlignedThreshold(0.5, false, 0.2, 0.6, 0.4);
* // result === [2, 0, -1, 1, 0, 0.25, -1, 1, 2, 0.5]
*/
Intersections2D.clipTriangleAtAxisAlignedThreshold = function(threshold, keepAbove, u0, u1, u2, result) {
if (!defined(threshold)) {
throw new DeveloperError('threshold is required.');
}
if (!defined(keepAbove)) {
throw new DeveloperError('keepAbove is required.');
}
if (!defined(u0)) {
throw new DeveloperError('u0 is required.');
}
if (!defined(u1)) {
throw new DeveloperError('u1 is required.');
}
if (!defined(u2)) {
throw new DeveloperError('u2 is required.');
}
if (!defined(result)) {
result = [];
} else {
result.length = 0;
}
var u0Behind;
var u1Behind;
var u2Behind;
if (keepAbove) {
u0Behind = u0 < threshold;
u1Behind = u1 < threshold;
u2Behind = u2 < threshold;
} else {
u0Behind = u0 > threshold;
u1Behind = u1 > threshold;
u2Behind = u2 > threshold;
}
var numBehind = u0Behind + u1Behind + u2Behind;
var u01Ratio;
var u02Ratio;
var u12Ratio;
var u10Ratio;
var u20Ratio;
var u21Ratio;
if (numBehind === 1) {
if (u0Behind) {
u01Ratio = (threshold - u0) / (u1 - u0);
u02Ratio = (threshold - u0) / (u2 - u0);
result.push(1);
result.push(2);
if (u02Ratio !== 1.0) {
result.push(-1);
result.push(0);
result.push(2);
result.push(u02Ratio);
}
if (u01Ratio !== 1.0) {
result.push(-1);
result.push(0);
result.push(1);
result.push(u01Ratio);
}
} else if (u1Behind) {
u12Ratio = (threshold - u1) / (u2 - u1);
u10Ratio = (threshold - u1) / (u0 - u1);
result.push(2);
result.push(0);
if (u10Ratio !== 1.0) {
result.push(-1);
result.push(1);
result.push(0);
result.push(u10Ratio);
}
if (u12Ratio !== 1.0) {
result.push(-1);
result.push(1);
result.push(2);
result.push(u12Ratio);
}
} else if (u2Behind) {
u20Ratio = (threshold - u2) / (u0 - u2);
u21Ratio = (threshold - u2) / (u1 - u2);
result.push(0);
result.push(1);
if (u21Ratio !== 1.0) {
result.push(-1);
result.push(2);
result.push(1);
result.push(u21Ratio);
}
if (u20Ratio !== 1.0) {
result.push(-1);
result.push(2);
result.push(0);
result.push(u20Ratio);
}
}
} else if (numBehind === 2) {
if (!u0Behind && u0 !== threshold) {
u10Ratio = (threshold - u1) / (u0 - u1);
u20Ratio = (threshold - u2) / (u0 - u2);
result.push(0);
result.push(-1);
result.push(1);
result.push(0);
result.push(u10Ratio);
result.push(-1);
result.push(2);
result.push(0);
result.push(u20Ratio);
} else if (!u1Behind && u1 !== threshold) {
u21Ratio = (threshold - u2) / (u1 - u2);
u01Ratio = (threshold - u0) / (u1 - u0);
result.push(1);
result.push(-1);
result.push(2);
result.push(1);
result.push(u21Ratio);
result.push(-1);
result.push(0);
result.push(1);
result.push(u01Ratio);
} else if (!u2Behind && u2 !== threshold) {
u02Ratio = (threshold - u0) / (u2 - u0);
u12Ratio = (threshold - u1) / (u2 - u1);
result.push(2);
result.push(-1);
result.push(0);
result.push(2);
result.push(u02Ratio);
result.push(-1);
result.push(1);
result.push(2);
result.push(u12Ratio);
}
} else if (numBehind !== 3) {
// Completely in front of threshold
result.push(0);
result.push(1);
result.push(2);
}
// else Completely behind threshold
return result;
};
/**
* Compute the barycentric coordinates of a 2D position within a 2D triangle.
*
* @param {Number} x The x coordinate of the position for which to find the barycentric coordinates.
* @param {Number} y The y coordinate of the position for which to find the barycentric coordinates.
* @param {Number} x1 The x coordinate of the triangle's first vertex.
* @param {Number} y1 The y coordinate of the triangle's first vertex.
* @param {Number} x2 The x coordinate of the triangle's second vertex.
* @param {Number} y2 The y coordinate of the triangle's second vertex.
* @param {Number} x3 The x coordinate of the triangle's third vertex.
* @param {Number} y3 The y coordinate of the triangle's third vertex.
* @param {Cartesian3} [result] The instance into to which to copy the result. If this parameter
* is undefined, a new instance is created and returned.
* @returns {Cartesian3} The barycentric coordinates of the position within the triangle.
*
* @example
* var result = Cesium.Intersections2D.computeBarycentricCoordinates(0.0, 0.0, 0.0, 1.0, -1, -0.5, 1, -0.5);
* // result === new Cesium.Cartesian3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0);
*/
Intersections2D.computeBarycentricCoordinates = function(x, y, x1, y1, x2, y2, x3, y3, result) {
if (!defined(x)) {
throw new DeveloperError('x is required.');
}
if (!defined(y)) {
throw new DeveloperError('y is required.');
}
if (!defined(x1)) {
throw new DeveloperError('x1 is required.');
}
if (!defined(y1)) {
throw new DeveloperError('y1 is required.');
}
if (!defined(x2)) {
throw new DeveloperError('x2 is required.');
}
if (!defined(y2)) {
throw new DeveloperError('y2 is required.');
}
if (!defined(x3)) {
throw new DeveloperError('x3 is required.');
}
if (!defined(y3)) {
throw new DeveloperError('y3 is required.');
}
var x1mx3 = x1 - x3;
var x3mx2 = x3 - x2;
var y2my3 = y2 - y3;
var y1my3 = y1 - y3;
var inverseDeterminant = 1.0 / (y2my3 * x1mx3 + x3mx2 * y1my3);
var ymy3 = y - y3;
var xmx3 = x - x3;
var l1 = (y2my3 * xmx3 + x3mx2 * ymy3) * inverseDeterminant;
var l2 = (-y1my3 * xmx3 + x1mx3 * ymy3) * inverseDeterminant;
var l3 = 1.0 - l1 - l2;
if (defined(result)) {
result.x = l1;
result.y = l2;
result.z = l3;
return result;
} else {
return new Cartesian3(l1, l2, l3);
}
};
return Intersections2D;
});
/*global define*/
define('Core/QuantizedMeshTerrainData',[
'../ThirdParty/when',
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./IndexDatatype',
'./Intersections2D',
'./Math',
'./OrientedBoundingBox',
'./TaskProcessor',
'./TerrainEncoding',
'./TerrainMesh'
], function(
when,
BoundingSphere,
Cartesian2,
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
IndexDatatype,
Intersections2D,
CesiumMath,
OrientedBoundingBox,
TaskProcessor,
TerrainEncoding,
TerrainMesh) {
'use strict';
/**
* Terrain data for a single tile where the terrain data is represented as a quantized mesh. A quantized
* mesh consists of three vertex attributes, longitude, latitude, and height. All attributes are expressed
* as 16-bit values in the range 0 to 32767. Longitude and latitude are zero at the southwest corner
* of the tile and 32767 at the northeast corner. Height is zero at the minimum height in the tile
* and 32767 at the maximum height in the tile.
*
* @alias QuantizedMeshTerrainData
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Uint16Array} options.quantizedVertices The buffer containing the quantized mesh.
* @param {Uint16Array|Uint32Array} options.indices The indices specifying how the quantized vertices are linked
* together into triangles. Each three indices specifies one triangle.
* @param {Number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid.
* @param {Number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid.
* @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh.
* @param {OrientedBoundingBox} [options.orientedBoundingBox] An OrientedBoundingBox bounding all of the vertices in the mesh.
* @param {Cartesian3} options.horizonOcclusionPoint The horizon occlusion point of the mesh. If this point
* is below the horizon, the entire tile is assumed to be below the horizon as well.
* The point is expressed in ellipsoid-scaled coordinates.
* @param {Number[]} options.westIndices The indices of the vertices on the western edge of the tile.
* @param {Number[]} options.southIndices The indices of the vertices on the southern edge of the tile.
* @param {Number[]} options.eastIndices The indices of the vertices on the eastern edge of the tile.
* @param {Number[]} options.northIndices The indices of the vertices on the northern edge of the tile.
* @param {Number} options.westSkirtHeight The height of the skirt to add on the western edge of the tile.
* @param {Number} options.southSkirtHeight The height of the skirt to add on the southern edge of the tile.
* @param {Number} options.eastSkirtHeight The height of the skirt to add on the eastern edge of the tile.
* @param {Number} options.northSkirtHeight The height of the skirt to add on the northern edge of the tile.
* @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
* If a child's bit is set, geometry will be requested for that tile as well when it
* is needed. If the bit is cleared, the child tile is not requested and geometry is
* instead upsampled from the parent. The bit values are as follows:
*
* Bit Position Bit Value Child Tile
* 0 1 Southwest
* 1 2 Southeast
* 2 4 Northwest
* 3 8 Northeast
*
* @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
* otherwise, false.
* @param {Uint8Array} [options.encodedNormals] The buffer containing per vertex normals, encoded using 'oct' encoding
* @param {Uint8Array} [options.waterMask] The buffer containing the watermask.
*
*
* @example
* var data = new Cesium.QuantizedMeshTerrainData({
* minimumHeight : -100,
* maximumHeight : 2101,
* quantizedVertices : new Uint16Array([// order is SW NW SE NE
* // longitude
* 0, 0, 32767, 32767,
* // latitude
* 0, 32767, 0, 32767,
* // heights
* 16384, 0, 32767, 16384]),
* indices : new Uint16Array([0, 3, 1,
* 0, 2, 3]),
* boundingSphere : new Cesium.BoundingSphere(new Cesium.Cartesian3(1.0, 2.0, 3.0), 10000),
* orientedBoundingBox : new Cesium.OrientedBoundingBox(new Cesium.Cartesian3(1.0, 2.0, 3.0), Cesium.Matrix3.fromRotationX(Cesium.Math.PI, new Cesium.Matrix3())),
* horizonOcclusionPoint : new Cesium.Cartesian3(3.0, 2.0, 1.0),
* westIndices : [0, 1],
* southIndices : [0, 1],
* eastIndices : [2, 3],
* northIndices : [1, 3],
* westSkirtHeight : 1.0,
* southSkirtHeight : 1.0,
* eastSkirtHeight : 1.0,
* northSkirtHeight : 1.0
* });
*
* @see TerrainData
* @see HeightmapTerrainData
*/
function QuantizedMeshTerrainData(options) {
if (!defined(options) || !defined(options.quantizedVertices)) {
throw new DeveloperError('options.quantizedVertices is required.');
}
if (!defined(options.indices)) {
throw new DeveloperError('options.indices is required.');
}
if (!defined(options.minimumHeight)) {
throw new DeveloperError('options.minimumHeight is required.');
}
if (!defined(options.maximumHeight)) {
throw new DeveloperError('options.maximumHeight is required.');
}
if (!defined(options.maximumHeight)) {
throw new DeveloperError('options.maximumHeight is required.');
}
if (!defined(options.boundingSphere)) {
throw new DeveloperError('options.boundingSphere is required.');
}
if (!defined(options.horizonOcclusionPoint)) {
throw new DeveloperError('options.horizonOcclusionPoint is required.');
}
if (!defined(options.westIndices)) {
throw new DeveloperError('options.westIndices is required.');
}
if (!defined(options.southIndices)) {
throw new DeveloperError('options.southIndices is required.');
}
if (!defined(options.eastIndices)) {
throw new DeveloperError('options.eastIndices is required.');
}
if (!defined(options.northIndices)) {
throw new DeveloperError('options.northIndices is required.');
}
if (!defined(options.westSkirtHeight)) {
throw new DeveloperError('options.westSkirtHeight is required.');
}
if (!defined(options.southSkirtHeight)) {
throw new DeveloperError('options.southSkirtHeight is required.');
}
if (!defined(options.eastSkirtHeight)) {
throw new DeveloperError('options.eastSkirtHeight is required.');
}
if (!defined(options.northSkirtHeight)) {
throw new DeveloperError('options.northSkirtHeight is required.');
}
this._quantizedVertices = options.quantizedVertices;
this._encodedNormals = options.encodedNormals;
this._indices = options.indices;
this._minimumHeight = options.minimumHeight;
this._maximumHeight = options.maximumHeight;
this._boundingSphere = options.boundingSphere;
this._orientedBoundingBox = options.orientedBoundingBox;
this._horizonOcclusionPoint = options.horizonOcclusionPoint;
var vertexCount = this._quantizedVertices.length / 3;
var uValues = this._uValues = this._quantizedVertices.subarray(0, vertexCount);
var vValues = this._vValues = this._quantizedVertices.subarray(vertexCount, 2 * vertexCount);
this._heightValues = this._quantizedVertices.subarray(2 * vertexCount, 3 * vertexCount);
// We don't assume that we can count on the edge vertices being sorted by u or v.
function sortByV(a, b) {
return vValues[a] - vValues[b];
}
function sortByU(a, b) {
return uValues[a] - uValues[b];
}
this._westIndices = sortIndicesIfNecessary(options.westIndices, sortByV, vertexCount);
this._southIndices = sortIndicesIfNecessary(options.southIndices, sortByU, vertexCount);
this._eastIndices = sortIndicesIfNecessary(options.eastIndices, sortByV, vertexCount);
this._northIndices = sortIndicesIfNecessary(options.northIndices, sortByU, vertexCount);
this._westSkirtHeight = options.westSkirtHeight;
this._southSkirtHeight = options.southSkirtHeight;
this._eastSkirtHeight = options.eastSkirtHeight;
this._northSkirtHeight = options.northSkirtHeight;
this._childTileMask = defaultValue(options.childTileMask, 15);
this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
this._waterMask = options.waterMask;
this._mesh = undefined;
}
defineProperties(QuantizedMeshTerrainData.prototype, {
/**
* The water mask included in this terrain data, if any. A water mask is a rectangular
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof QuantizedMeshTerrainData.prototype
* @type {Uint8Array|Image|Canvas}
*/
waterMask : {
get : function() {
return this._waterMask;
}
}
});
var arrayScratch = [];
function sortIndicesIfNecessary(indices, sortFunction, vertexCount) {
arrayScratch.length = indices.length;
var needsSort = false;
for (var i = 0, len = indices.length; i < len; ++i) {
arrayScratch[i] = indices[i];
needsSort = needsSort || (i > 0 && sortFunction(indices[i - 1], indices[i]) > 0);
}
if (needsSort) {
arrayScratch.sort(sortFunction);
return IndexDatatype.createTypedArray(vertexCount, arrayScratch);
} else {
return indices;
}
}
var createMeshTaskProcessor = new TaskProcessor('createVerticesFromQuantizedTerrainMesh');
/**
* Creates a {@link TerrainMesh} from this terrain data.
*
* @private
*
* @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
* @param {Number} x The X coordinate of the tile for which to create the terrain data.
* @param {Number} y The Y coordinate of the tile for which to create the terrain data.
* @param {Number} level The level of the tile for which to create the terrain data.
* @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain.
* @returns {Promise.|undefined} A promise for the terrain mesh, or undefined if too many
* asynchronous mesh creations are already in progress and the operation should
* be retried later.
*/
QuantizedMeshTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) {
if (!defined(tilingScheme)) {
throw new DeveloperError('tilingScheme is required.');
}
if (!defined(x)) {
throw new DeveloperError('x is required.');
}
if (!defined(y)) {
throw new DeveloperError('y is required.');
}
if (!defined(level)) {
throw new DeveloperError('level is required.');
}
var ellipsoid = tilingScheme.ellipsoid;
var rectangle = tilingScheme.tileXYToRectangle(x, y, level);
exaggeration = defaultValue(exaggeration, 1.0);
var verticesPromise = createMeshTaskProcessor.scheduleTask({
minimumHeight : this._minimumHeight,
maximumHeight : this._maximumHeight,
quantizedVertices : this._quantizedVertices,
octEncodedNormals : this._encodedNormals,
includeWebMercatorT : true,
indices : this._indices,
westIndices : this._westIndices,
southIndices : this._southIndices,
eastIndices : this._eastIndices,
northIndices : this._northIndices,
westSkirtHeight : this._westSkirtHeight,
southSkirtHeight : this._southSkirtHeight,
eastSkirtHeight : this._eastSkirtHeight,
northSkirtHeight : this._northSkirtHeight,
rectangle : rectangle,
relativeToCenter : this._boundingSphere.center,
ellipsoid : ellipsoid,
exaggeration : exaggeration
});
if (!defined(verticesPromise)) {
// Postponed
return undefined;
}
var that = this;
return when(verticesPromise, function(result) {
var vertexCount = that._quantizedVertices.length / 3;
vertexCount += that._westIndices.length + that._southIndices.length + that._eastIndices.length + that._northIndices.length;
var indicesTypedArray = IndexDatatype.createTypedArray(vertexCount, result.indices);
var vertices = new Float32Array(result.vertices);
var rtc = result.center;
var minimumHeight = result.minimumHeight;
var maximumHeight = result.maximumHeight;
var boundingSphere = defaultValue(result.boundingSphere, that._boundingSphere);
var obb = defaultValue(result.orientedBoundingBox, that._orientedBoundingBox);
var occlusionPoint = that._horizonOcclusionPoint;
var stride = result.vertexStride;
var terrainEncoding = TerrainEncoding.clone(result.encoding);
that._skirtIndex = result.skirtIndex;
that._vertexCountWithoutSkirts = that._quantizedVertices.length / 3;
that._mesh = new TerrainMesh(
rtc,
vertices,
indicesTypedArray,
minimumHeight,
maximumHeight,
boundingSphere,
occlusionPoint,
stride,
obb,
terrainEncoding,
exaggeration);
// Free memory received from server after mesh is created.
that._quantizedVertices = undefined;
that._encodedNormals = undefined;
that._indices = undefined;
that._uValues = undefined;
that._vValues = undefined;
that._heightValues = undefined;
that._westIndices = undefined;
that._southIndices = undefined;
that._eastIndices = undefined;
that._northIndices = undefined;
return that._mesh;
});
};
var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh');
/**
* Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the
* vertices in this instance, interpolated if necessary.
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {Number} thisX The X coordinate of this tile in the tiling scheme.
* @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {Number} thisLevel The level of this tile in the tiling scheme.
* @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
* or undefined if too many asynchronous upsample operations are in progress and the request has been
* deferred.
*/
QuantizedMeshTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) {
if (!defined(tilingScheme)) {
throw new DeveloperError('tilingScheme is required.');
}
if (!defined(thisX)) {
throw new DeveloperError('thisX is required.');
}
if (!defined(thisY)) {
throw new DeveloperError('thisY is required.');
}
if (!defined(thisLevel)) {
throw new DeveloperError('thisLevel is required.');
}
if (!defined(descendantX)) {
throw new DeveloperError('descendantX is required.');
}
if (!defined(descendantY)) {
throw new DeveloperError('descendantY is required.');
}
if (!defined(descendantLevel)) {
throw new DeveloperError('descendantLevel is required.');
}
var levelDifference = descendantLevel - thisLevel;
if (levelDifference > 1) {
throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.');
}
var mesh = this._mesh;
if (!defined(this._mesh)) {
return undefined;
}
var isEastChild = thisX * 2 !== descendantX;
var isNorthChild = thisY * 2 === descendantY;
var ellipsoid = tilingScheme.ellipsoid;
var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel);
var upsamplePromise = upsampleTaskProcessor.scheduleTask({
vertices : mesh.vertices,
vertexCountWithoutSkirts : this._vertexCountWithoutSkirts,
indices : mesh.indices,
skirtIndex : this._skirtIndex,
encoding : mesh.encoding,
minimumHeight : this._minimumHeight,
maximumHeight : this._maximumHeight,
isEastChild : isEastChild,
isNorthChild : isNorthChild,
childRectangle : childRectangle,
ellipsoid : ellipsoid,
exaggeration : mesh.exaggeration
});
if (!defined(upsamplePromise)) {
// Postponed
return undefined;
}
var shortestSkirt = Math.min(this._westSkirtHeight, this._eastSkirtHeight);
shortestSkirt = Math.min(shortestSkirt, this._southSkirtHeight);
shortestSkirt = Math.min(shortestSkirt, this._northSkirtHeight);
var westSkirtHeight = isEastChild ? (shortestSkirt * 0.5) : this._westSkirtHeight;
var southSkirtHeight = isNorthChild ? (shortestSkirt * 0.5) : this._southSkirtHeight;
var eastSkirtHeight = isEastChild ? this._eastSkirtHeight : (shortestSkirt * 0.5);
var northSkirtHeight = isNorthChild ? this._northSkirtHeight : (shortestSkirt * 0.5);
return when(upsamplePromise, function(result) {
var quantizedVertices = new Uint16Array(result.vertices);
var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices);
var encodedNormals;
if (defined(result.encodedNormals)) {
encodedNormals = new Uint8Array(result.encodedNormals);
}
return new QuantizedMeshTerrainData({
quantizedVertices : quantizedVertices,
indices : indicesTypedArray,
encodedNormals : encodedNormals,
minimumHeight : result.minimumHeight,
maximumHeight : result.maximumHeight,
boundingSphere : BoundingSphere.clone(result.boundingSphere),
orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox),
horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint),
westIndices : result.westIndices,
southIndices : result.southIndices,
eastIndices : result.eastIndices,
northIndices : result.northIndices,
westSkirtHeight : westSkirtHeight,
southSkirtHeight : southSkirtHeight,
eastSkirtHeight : eastSkirtHeight,
northSkirtHeight : northSkirtHeight,
childTileMask : 0,
createdByUpsampling : true
});
});
};
var maxShort = 32767;
var barycentricCoordinateScratch = new Cartesian3();
/**
* Computes the terrain height at a specified longitude and latitude.
*
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
* @param {Number} longitude The longitude in radians.
* @param {Number} latitude The latitude in radians.
* @returns {Number} The terrain height at the specified position. The position is clamped to
* the rectangle, so expect incorrect results for positions far outside the rectangle.
*/
QuantizedMeshTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) {
var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0);
u *= maxShort;
var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0);
v *= maxShort;
if (!defined(this._mesh)) {
return interpolateHeight(this, u, v);
}
interpolateMeshHeight(this, u, v);
};
var texCoordScratch0 = new Cartesian2();
var texCoordScratch1 = new Cartesian2();
var texCoordScratch2 = new Cartesian2();
function interpolateMeshHeight(terrainData, u, v) {
var mesh = terrainData._mesh;
var vertices = mesh.vertices;
var encoding = mesh.encoding;
var indices = mesh.indices;
for (var i = 0, len = indices.length; i < len; i += 3) {
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var uv0 = encoding.decodeTextureCoordinates(vertices, i0, texCoordScratch0);
var uv1 = encoding.decodeTextureCoordinates(vertices, i1, texCoordScratch1);
var uv2 = encoding.decodeTextureCoordinates(vertices, i2, texCoordScratch2);
var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, uv0.x, uv0.y, uv1.x, uv1.y, uv2.x, uv2.y, barycentricCoordinateScratch);
if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) {
var h0 = encoding.decodeHeight(vertices, i0);
var h1 = encoding.decodeHeight(vertices, i1);
var h2 = encoding.decodeHeight(vertices, i2);
return barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2;
}
}
// Position does not lie in any triangle in this mesh.
return undefined;
}
function interpolateHeight(terrainData, u, v) {
var uBuffer = terrainData._uValues;
var vBuffer = terrainData._vValues;
var heightBuffer = terrainData._heightValues;
var indices = terrainData._indices;
for (var i = 0, len = indices.length; i < len; i += 3) {
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var u0 = uBuffer[i0];
var u1 = uBuffer[i1];
var u2 = uBuffer[i2];
var v0 = vBuffer[i0];
var v1 = vBuffer[i1];
var v2 = vBuffer[i2];
var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, u0, v0, u1, v1, u2, v2, barycentricCoordinateScratch);
if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) {
var quantizedHeight = barycentric.x * heightBuffer[i0] +
barycentric.y * heightBuffer[i1] +
barycentric.z * heightBuffer[i2];
return CesiumMath.lerp(terrainData._minimumHeight, terrainData._maximumHeight, quantizedHeight / maxShort);
}
}
// Position does not lie in any triangle in this mesh.
return undefined;
}
/**
* Determines if a given child tile is available, based on the
* {@link HeightmapTerrainData.childTileMask}. The given child tile coordinates are assumed
* to be one of the four children of this tile. If non-child tile coordinates are
* given, the availability of the southeast child tile is returned.
*
* @param {Number} thisX The tile X coordinate of this (the parent) tile.
* @param {Number} thisY The tile Y coordinate of this (the parent) tile.
* @param {Number} childX The tile X coordinate of the child tile to check for availability.
* @param {Number} childY The tile Y coordinate of the child tile to check for availability.
* @returns {Boolean} True if the child tile is available; otherwise, false.
*/
QuantizedMeshTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) {
if (!defined(thisX)) {
throw new DeveloperError('thisX is required.');
}
if (!defined(thisY)) {
throw new DeveloperError('thisY is required.');
}
if (!defined(childX)) {
throw new DeveloperError('childX is required.');
}
if (!defined(childY)) {
throw new DeveloperError('childY is required.');
}
var bitNumber = 2; // northwest child
if (childX !== thisX * 2) {
++bitNumber; // east child
}
if (childY !== thisY * 2) {
bitNumber -= 2; // south child
}
return (this._childTileMask & (1 << bitNumber)) !== 0;
};
/**
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
* terrain data. If this value is false, the data was obtained from some other source, such
* as by downloading it from a remote server. This method should return true for instances
* returned from a call to {@link HeightmapTerrainData#upsample}.
*
* @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
*/
QuantizedMeshTerrainData.prototype.wasCreatedByUpsampling = function() {
return this._createdByUpsampling;
};
return QuantizedMeshTerrainData;
});
/*global define*/
define('Core/formatError',[
'./defined'
], function(
defined) {
'use strict';
/**
* Formats an error object into a String. If available, uses name, message, and stack
* properties, otherwise, falls back on toString().
*
* @exports formatError
*
* @param {Object} object The item to find in the array.
* @returns {String} A string containing the formatted error.
*/
function formatError(object) {
var result;
var name = object.name;
var message = object.message;
if (defined(name) && defined(message)) {
result = name + ': ' + message;
} else {
result = object.toString();
}
var stack = object.stack;
if (defined(stack)) {
result += '\n' + stack;
}
return result;
}
return formatError;
});
/*global define*/
define('Core/TileProviderError',[
'./defaultValue',
'./defined',
'./formatError'
], function(
defaultValue,
defined,
formatError) {
'use strict';
/**
* Provides details about an error that occurred in an {@link ImageryProvider} or a {@link TerrainProvider}.
*
* @alias TileProviderError
* @constructor
*
* @param {ImageryProvider|TerrainProvider} provider The imagery or terrain provider that experienced the error.
* @param {String} message A message describing the error.
* @param {Number} [x] The X coordinate of the tile that experienced the error, or undefined if the error
* is not specific to a particular tile.
* @param {Number} [y] The Y coordinate of the tile that experienced the error, or undefined if the error
* is not specific to a particular tile.
* @param {Number} [level] The level of the tile that experienced the error, or undefined if the error
* is not specific to a particular tile.
* @param {Number} [timesRetried=0] The number of times this operation has been retried.
* @param {Error} [error] The error or exception that occurred, if any.
*/
function TileProviderError(provider, message, x, y, level, timesRetried, error) {
/**
* The {@link ImageryProvider} or {@link TerrainProvider} that experienced the error.
* @type {ImageryProvider|TerrainProvider}
*/
this.provider = provider;
/**
* The message describing the error.
* @type {String}
*/
this.message = message;
/**
* The X coordinate of the tile that experienced the error. If the error is not specific
* to a particular tile, this property will be undefined.
* @type {Number}
*/
this.x = x;
/**
* The Y coordinate of the tile that experienced the error. If the error is not specific
* to a particular tile, this property will be undefined.
* @type {Number}
*/
this.y = y;
/**
* The level-of-detail of the tile that experienced the error. If the error is not specific
* to a particular tile, this property will be undefined.
* @type {Number}
*/
this.level = level;
/**
* The number of times this operation has been retried.
* @type {Number}
* @default 0
*/
this.timesRetried = defaultValue(timesRetried, 0);
/**
* True if the failed operation should be retried; otherwise, false. The imagery or terrain provider
* will set the initial value of this property before raising the event, but any listeners
* can change it. The value after the last listener is invoked will be acted upon.
* @type {Boolean}
* @default false
*/
this.retry = false;
/**
* The error or exception that occurred, if any.
* @type {Error}
*/
this.error = error;
}
/**
* Handles an error in an {@link ImageryProvider} or {@link TerrainProvider} by raising an event if it has any listeners, or by
* logging the error to the console if the event has no listeners. This method also tracks the number
* of times the operation has been retried and will automatically retry if requested to do so by the
* event listeners.
*
* @param {TileProviderError} previousError The error instance returned by this function the last
* time it was called for this error, or undefined if this is the first time this error has
* occurred.
* @param {ImageryProvider|TerrainProvider} provider The imagery or terrain provider that encountered the error.
* @param {Event} event The event to raise to inform listeners of the error.
* @param {String} message The message describing the error.
* @param {Number} x The X coordinate of the tile that experienced the error, or undefined if the
* error is not specific to a particular tile.
* @param {Number} y The Y coordinate of the tile that experienced the error, or undefined if the
* error is not specific to a particular tile.
* @param {Number} level The level-of-detail of the tile that experienced the error, or undefined if the
* error is not specific to a particular tile.
* @param {TileProviderError~RetryFunction} retryFunction The function to call to retry the operation. If undefined, the
* operation will not be retried.
* @param {Error} [errorDetails] The error or exception that occurred, if any.
* @returns {TileProviderError} The error instance that was passed to the event listeners and that
* should be passed to this function the next time it is called for the same error in order
* to track retry counts.
*/
TileProviderError.handleError = function(previousError, provider, event, message, x, y, level, retryFunction, errorDetails) {
var error = previousError;
if (!defined(previousError)) {
error = new TileProviderError(provider, message, x, y, level, 0, errorDetails);
} else {
error.provider = provider;
error.message = message;
error.x = x;
error.y = y;
error.level = level;
error.retry = false;
error.error = errorDetails;
++error.timesRetried;
}
if (event.numberOfListeners > 0) {
event.raiseEvent(error);
} else {
console.log('An error occurred in "' + provider.constructor.name + '": ' + formatError(message));
}
if (error.retry && defined(retryFunction)) {
retryFunction();
}
return error;
};
/**
* Handles success of an operation by resetting the retry count of a previous error, if any. This way,
* if the error occurs again in the future, the listeners will be informed that it has not yet been retried.
*
* @param {TileProviderError} previousError The previous error, or undefined if this operation has
* not previously resulted in an error.
*/
TileProviderError.handleSuccess = function(previousError) {
if (defined(previousError)) {
previousError.timesRetried = -1;
}
};
/**
* A function that will be called to retry the operation.
* @callback TileProviderError~RetryFunction
*/
return TileProviderError;
});
/*global define*/
define('Core/CesiumTerrainProvider',[
'../ThirdParty/Uri',
'../ThirdParty/when',
'./BoundingSphere',
'./Cartesian3',
'./Credit',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Event',
'./GeographicTilingScheme',
'./HeightmapTerrainData',
'./IndexDatatype',
'./joinUrls',
'./loadArrayBuffer',
'./loadJson',
'./Math',
'./OrientedBoundingBox',
'./QuantizedMeshTerrainData',
'./TerrainProvider',
'./throttleRequestByServer',
'./TileProviderError'
], function(
Uri,
when,
BoundingSphere,
Cartesian3,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
GeographicTilingScheme,
HeightmapTerrainData,
IndexDatatype,
joinUrls,
loadArrayBuffer,
loadJson,
CesiumMath,
OrientedBoundingBox,
QuantizedMeshTerrainData,
TerrainProvider,
throttleRequestByServer,
TileProviderError) {
'use strict';
/**
* A {@link TerrainProvider} that access terrain data in a Cesium terrain format.
* The format is described on the
* {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Cesium-Terrain-Server|Cesium wiki}.
*
* @alias CesiumTerrainProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The URL of the Cesium terrain server.
* @param {Proxy} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
* @param {Boolean} [options.requestVertexNormals=false] Flag that indicates if the client should request additional lighting information from the server, in the form of per vertex normals if available.
* @param {Boolean} [options.requestWaterMask=false] Flag that indicates if the client should request per tile water masks from the server, if available.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
*
*
* @example
* // Construct a terrain provider that uses per vertex normals for lighting
* // to add shading detail to an imagery provider.
* var terrainProvider = new Cesium.CesiumTerrainProvider({
* url : 'https://assets.agi.com/stk-terrain/world',
* requestVertexNormals : true
* });
*
* // Terrain geometry near the surface of the globe is difficult to view when using NaturalEarthII imagery,
* // unless the TerrainProvider provides additional lighting information to shade the terrain (as shown above).
* var imageryProvider = Cesium.createTileMapServiceImageryProvider({
* url : 'http://localhost:8080/Source/Assets/Textures/NaturalEarthII',
* fileExtension : 'jpg'
* });
*
* var viewer = new Cesium.Viewer('cesiumContainer', {
* imageryProvider : imageryProvider,
* baseLayerPicker : false,
* terrainProvider : terrainProvider
* });
*
* // The globe must enable lighting to make use of the terrain's vertex normals
* viewer.scene.globe.enableLighting = true;
*
* @see TerrainProvider
*/
function CesiumTerrainProvider(options) {
if (!defined(options) || !defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
this._url = options.url;
this._proxy = options.proxy;
this._tilingScheme = new GeographicTilingScheme({
numberOfLevelZeroTilesX : 2,
numberOfLevelZeroTilesY : 1,
ellipsoid : options.ellipsoid
});
this._heightmapWidth = 65;
this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._heightmapWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0));
this._heightmapStructure = undefined;
this._hasWaterMask = false;
/**
* Boolean flag that indicates if the Terrain Server can provide vertex normals.
* @type {Boolean}
* @default false
* @private
*/
this._hasVertexNormals = false;
/**
* Boolean flag that indicates if the client should request vertex normals from the server.
* @type {Boolean}
* @default false
* @private
*/
this._requestVertexNormals = defaultValue(options.requestVertexNormals, false);
this._littleEndianExtensionSize = true;
/**
* Boolean flag that indicates if the client should request tile watermasks from the server.
* @type {Boolean}
* @default false
* @private
*/
this._requestWaterMask = defaultValue(options.requestWaterMask, false);
this._errorEvent = new Event();
var credit = options.credit;
if (typeof credit === 'string') {
credit = new Credit(credit);
}
this._credit = credit;
this._ready = false;
this._readyPromise = when.defer();
var metadataUrl = joinUrls(this._url, 'layer.json');
if (defined(this._proxy)) {
metadataUrl = this._proxy.getURL(metadataUrl);
}
var that = this;
var metadataError;
function metadataSuccess(data) {
var message;
if (!data.format) {
message = 'The tile format is not specified in the layer.json file.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
return;
}
if (!data.tiles || data.tiles.length === 0) {
message = 'The layer.json file does not specify any tile URL templates.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
return;
}
if (data.format === 'heightmap-1.0') {
that._heightmapStructure = {
heightScale : 1.0 / 5.0,
heightOffset : -1000.0,
elementsPerHeight : 1,
stride : 1,
elementMultiplier : 256.0,
isBigEndian : false,
lowestEncodedHeight : 0,
highestEncodedHeight : 256 * 256 - 1
};
that._hasWaterMask = true;
that._requestWaterMask = true;
} else if (data.format.indexOf('quantized-mesh-1.') !== 0) {
message = 'The tile format "' + data.format + '" is invalid or not supported.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
return;
}
that._tileUrlTemplates = data.tiles;
for (var i = 0; i < that._tileUrlTemplates.length; ++i) {
var template = new Uri(that._tileUrlTemplates[i]);
var baseUri = new Uri(that._url);
if (template.authority && !baseUri.authority) {
baseUri.authority = template.authority;
baseUri.scheme = template.scheme;
}
that._tileUrlTemplates[i] = joinUrls(baseUri, template).toString().replace('{version}', data.version);
}
that._availableTiles = data.available;
if (!defined(that._credit) && defined(data.attribution) && data.attribution !== null) {
that._credit = new Credit(data.attribution);
}
// The vertex normals defined in the 'octvertexnormals' extension is identical to the original
// contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now
// deprecated, as the extensionLength for this extension was incorrectly using big endian.
// We maintain backwards compatibility with the legacy 'vertexnormal' implementation
// by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals'
// over 'vertexnormals' if both extensions are supported by the server.
if (defined(data.extensions) && data.extensions.indexOf('octvertexnormals') !== -1) {
that._hasVertexNormals = true;
} else if (defined(data.extensions) && data.extensions.indexOf('vertexnormals') !== -1) {
that._hasVertexNormals = true;
that._littleEndianExtensionSize = false;
}
if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) {
that._hasWaterMask = true;
}
that._ready = true;
that._readyPromise.resolve(true);
}
function metadataFailure(data) {
// If the metadata is not found, assume this is a pre-metadata heightmap tileset.
if (defined(data) && data.statusCode === 404) {
metadataSuccess({
tilejson: '2.1.0',
format : 'heightmap-1.0',
version : '1.0.0',
scheme : 'tms',
tiles : [
'{z}/{x}/{y}.terrain?v={version}'
]
});
return;
}
var message = 'An error occurred while accessing ' + metadataUrl + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
}
function requestMetadata() {
var metadata = loadJson(metadataUrl);
when(metadata, metadataSuccess, metadataFailure);
}
requestMetadata();
}
/**
* When using the Quantized-Mesh format, a tile may be returned that includes additional extensions, such as PerVertexNormals, watermask, etc.
* This enumeration defines the unique identifiers for each type of extension data that has been appended to the standard mesh data.
*
* @exports QuantizedMeshExtensionIds
* @see CesiumTerrainProvider
* @private
*/
var QuantizedMeshExtensionIds = {
/**
* Oct-Encoded Per-Vertex Normals are included as an extension to the tile mesh
*
* @type {Number}
* @constant
* @default 1
*/
OCT_VERTEX_NORMALS: 1,
/**
* A watermask is included as an extension to the tile mesh
*
* @type {Number}
* @constant
* @default 2
*/
WATER_MASK: 2
};
function getRequestHeader(extensionsList) {
if (!defined(extensionsList) || extensionsList.length === 0) {
return {
Accept : 'application/vnd.quantized-mesh,application/octet-stream;q=0.9,*/*;q=0.01'
};
} else {
var extensions = extensionsList.join('-');
return {
Accept : 'application/vnd.quantized-mesh;extensions=' + extensions + ',application/octet-stream;q=0.9,*/*;q=0.01'
};
}
}
function createHeightmapTerrainData(provider, buffer, level, x, y, tmsY) {
var heightBuffer = new Uint16Array(buffer, 0, provider._heightmapWidth * provider._heightmapWidth);
return new HeightmapTerrainData({
buffer : heightBuffer,
childTileMask : new Uint8Array(buffer, heightBuffer.byteLength, 1)[0],
waterMask : new Uint8Array(buffer, heightBuffer.byteLength + 1, buffer.byteLength - heightBuffer.byteLength - 1),
width : provider._heightmapWidth,
height : provider._heightmapWidth,
structure : provider._heightmapStructure
});
}
function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY) {
var pos = 0;
var cartesian3Elements = 3;
var boundingSphereElements = cartesian3Elements + 1;
var cartesian3Length = Float64Array.BYTES_PER_ELEMENT * cartesian3Elements;
var boundingSphereLength = Float64Array.BYTES_PER_ELEMENT * boundingSphereElements;
var encodedVertexElements = 3;
var encodedVertexLength = Uint16Array.BYTES_PER_ELEMENT * encodedVertexElements;
var triangleElements = 3;
var bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT;
var triangleLength = bytesPerIndex * triangleElements;
var view = new DataView(buffer);
var center = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true));
pos += cartesian3Length;
var minimumHeight = view.getFloat32(pos, true);
pos += Float32Array.BYTES_PER_ELEMENT;
var maximumHeight = view.getFloat32(pos, true);
pos += Float32Array.BYTES_PER_ELEMENT;
var boundingSphere = new BoundingSphere(
new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true)),
view.getFloat64(pos + cartesian3Length, true));
pos += boundingSphereLength;
var horizonOcclusionPoint = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true));
pos += cartesian3Length;
var vertexCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var encodedVertexBuffer = new Uint16Array(buffer, pos, vertexCount * 3);
pos += vertexCount * encodedVertexLength;
if (vertexCount > 64 * 1024) {
// More than 64k vertices, so indices are 32-bit.
bytesPerIndex = Uint32Array.BYTES_PER_ELEMENT;
triangleLength = bytesPerIndex * triangleElements;
}
// Decode the vertex buffer.
var uBuffer = encodedVertexBuffer.subarray(0, vertexCount);
var vBuffer = encodedVertexBuffer.subarray(vertexCount, 2 * vertexCount);
var heightBuffer = encodedVertexBuffer.subarray(vertexCount * 2, 3 * vertexCount);
var i;
var u = 0;
var v = 0;
var height = 0;
function zigZagDecode(value) {
return (value >> 1) ^ (-(value & 1));
}
for (i = 0; i < vertexCount; ++i) {
u += zigZagDecode(uBuffer[i]);
v += zigZagDecode(vBuffer[i]);
height += zigZagDecode(heightBuffer[i]);
uBuffer[i] = u;
vBuffer[i] = v;
heightBuffer[i] = height;
}
// skip over any additional padding that was added for 2/4 byte alignment
if (pos % bytesPerIndex !== 0) {
pos += (bytesPerIndex - (pos % bytesPerIndex));
}
var triangleCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var indices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, triangleCount * triangleElements);
pos += triangleCount * triangleLength;
// High water mark decoding based on decompressIndices_ in webgl-loader's loader.js.
// https://code.google.com/p/webgl-loader/source/browse/trunk/samples/loader.js?r=99#55
// Copyright 2012 Google Inc., Apache 2.0 license.
var highest = 0;
for (i = 0; i < indices.length; ++i) {
var code = indices[i];
indices[i] = highest - code;
if (code === 0) {
++highest;
}
}
var westVertexCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var westIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, westVertexCount);
pos += westVertexCount * bytesPerIndex;
var southVertexCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var southIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, southVertexCount);
pos += southVertexCount * bytesPerIndex;
var eastVertexCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var eastIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, eastVertexCount);
pos += eastVertexCount * bytesPerIndex;
var northVertexCount = view.getUint32(pos, true);
pos += Uint32Array.BYTES_PER_ELEMENT;
var northIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, northVertexCount);
pos += northVertexCount * bytesPerIndex;
var encodedNormalBuffer;
var waterMaskBuffer;
while (pos < view.byteLength) {
var extensionId = view.getUint8(pos, true);
pos += Uint8Array.BYTES_PER_ELEMENT;
var extensionLength = view.getUint32(pos, provider._littleEndianExtensionSize);
pos += Uint32Array.BYTES_PER_ELEMENT;
if (extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS && provider._requestVertexNormals) {
encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2);
} else if (extensionId === QuantizedMeshExtensionIds.WATER_MASK && provider._requestWaterMask) {
waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength);
}
pos += extensionLength;
}
var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0;
var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level);
var orientedBoundingBox;
if (rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) {
// Here, rectangle.width < pi/2, and rectangle.height < pi
// (though it would still work with rectangle.width up to pi)
// The skirt is not included in the OBB computation. If this ever
// causes any rendering artifacts (cracks), they are expected to be
// minor and in the corners of the screen. It's possible that this
// might need to be changed - just change to `minimumHeight - skirtHeight`
// A similar change might also be needed in `upsampleQuantizedTerrainMesh.js`.
orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, provider._tilingScheme.ellipsoid);
}
return new QuantizedMeshTerrainData({
center : center,
minimumHeight : minimumHeight,
maximumHeight : maximumHeight,
boundingSphere : boundingSphere,
orientedBoundingBox : orientedBoundingBox,
horizonOcclusionPoint : horizonOcclusionPoint,
quantizedVertices : encodedVertexBuffer,
encodedNormals : encodedNormalBuffer,
indices : indices,
westIndices : westIndices,
southIndices : southIndices,
eastIndices : eastIndices,
northIndices : northIndices,
westSkirtHeight : skirtHeight,
southSkirtHeight : skirtHeight,
eastSkirtHeight : skirtHeight,
northSkirtHeight : skirtHeight,
childTileMask: getChildMaskForTile(provider, level, x, tmsY),
waterMask: waterMaskBuffer
});
}
/**
* Requests the geometry for a given tile. This function should not be called before
* {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and
* may optionally include a water mask and an indication of which child tiles are available.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
* or false if the request should be initiated regardless of the number of requests
* already in progress.
* @returns {Promise.|undefined} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*
* @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready}
* returns true.
*/
CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
if (!this._ready) {
throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.');
}
var urlTemplates = this._tileUrlTemplates;
if (urlTemplates.length === 0) {
return undefined;
}
var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);
var tmsY = (yTiles - y - 1);
var url = urlTemplates[(x + tmsY + level) % urlTemplates.length].replace('{z}', level).replace('{x}', x).replace('{y}', tmsY);
var proxy = this._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
var promise;
var extensionList = [];
if (this._requestVertexNormals && this._hasVertexNormals) {
extensionList.push(this._littleEndianExtensionSize ? "octvertexnormals" : "vertexnormals");
}
if (this._requestWaterMask && this._hasWaterMask) {
extensionList.push("watermask");
}
function tileLoader(tileUrl) {
return loadArrayBuffer(tileUrl, getRequestHeader(extensionList));
}
throttleRequests = defaultValue(throttleRequests, true);
if (throttleRequests) {
promise = throttleRequestByServer(url, tileLoader);
if (!defined(promise)) {
return undefined;
}
} else {
promise = tileLoader(url);
}
var that = this;
return when(promise, function(buffer) {
if (defined(that._heightmapStructure)) {
return createHeightmapTerrainData(that, buffer, level, x, y, tmsY);
} else {
return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY);
}
});
};
defineProperties(CesiumTerrainProvider.prototype, {
/**
* Gets an event that is raised when the terrain provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof CesiumTerrainProvider.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets the credit to display when this terrain provider is active. Typically this is used to credit
* the source of the terrain. This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
* @memberof CesiumTerrainProvider.prototype
* @type {Credit}
*/
credit : {
get : function() {
if (!this._ready) {
throw new DeveloperError('credit must not be called before the terrain provider is ready.');
}
return this._credit;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link CesiumTerrainProvider#ready} returns true.
* @memberof CesiumTerrainProvider.prototype
* @type {GeographicTilingScheme}
*/
tilingScheme : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tilingScheme must not be called before the terrain provider is ready.');
}
return this._tilingScheme;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof CesiumTerrainProvider.prototype
* @type {Boolean}
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof CesiumTerrainProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Gets a value indicating whether or not the provider includes a water mask. The water mask
* indicates which areas of the globe are water rather than land, so they can be rendered
* as a reflective surface with animated waves. This function should not be
* called before {@link CesiumTerrainProvider#ready} returns true.
* @memberof CesiumTerrainProvider.prototype
* @type {Boolean}
* @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
*/
hasWaterMask : {
get : function() {
if (!this._ready) {
throw new DeveloperError('hasWaterMask must not be called before the terrain provider is ready.');
}
return this._hasWaterMask && this._requestWaterMask;
}
},
/**
* Gets a value indicating whether or not the requested tiles include vertex normals.
* This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
* @memberof CesiumTerrainProvider.prototype
* @type {Boolean}
* @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
*/
hasVertexNormals : {
get : function() {
if (!this._ready) {
throw new DeveloperError('hasVertexNormals must not be called before the terrain provider is ready.');
}
// returns true if we can request vertex normals from the server
return this._hasVertexNormals && this._requestVertexNormals;
}
},
/**
* Boolean flag that indicates if the client should request vertex normals from the server.
* Vertex normals data is appended to the standard tile mesh data only if the client requests the vertex normals and
* if the server provides vertex normals.
* @memberof CesiumTerrainProvider.prototype
* @type {Boolean}
*/
requestVertexNormals : {
get : function() {
return this._requestVertexNormals;
}
},
/**
* Boolean flag that indicates if the client should request a watermask from the server.
* Watermask data is appended to the standard tile mesh data only if the client requests the watermask and
* if the server provides a watermask.
* @memberof CesiumTerrainProvider.prototype
* @type {Boolean}
*/
requestWaterMask : {
get : function() {
return this._requestWaterMask;
}
}
});
/**
* Gets the maximum geometric error allowed in a tile at a given level.
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error.
*/
CesiumTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
return this._levelZeroMaximumGeometricError / (1 << level);
};
function getChildMaskForTile(terrainProvider, level, x, y) {
var available = terrainProvider._availableTiles;
if (!available || available.length === 0) {
return 15;
}
var childLevel = level + 1;
if (childLevel >= available.length) {
return 0;
}
var levelAvailable = available[childLevel];
var mask = 0;
mask |= isTileInRange(levelAvailable, 2 * x, 2 * y) ? 1 : 0;
mask |= isTileInRange(levelAvailable, 2 * x + 1, 2 * y) ? 2 : 0;
mask |= isTileInRange(levelAvailable, 2 * x, 2 * y + 1) ? 4 : 0;
mask |= isTileInRange(levelAvailable, 2 * x + 1, 2 * y + 1) ? 8 : 0;
return mask;
}
function isTileInRange(levelAvailable, x, y) {
for (var i = 0, len = levelAvailable.length; i < len; ++i) {
var range = levelAvailable[i];
if (x >= range.startX && x <= range.endX && y >= range.startY && y <= range.endY) {
return true;
}
}
return false;
}
/**
* Determines whether data for a tile is available to be loaded.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @returns {Boolean} Undefined if not supported, otherwise true or false.
*/
CesiumTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
var available = this._availableTiles;
if (!available || available.length === 0) {
return undefined;
} else {
if (level >= available.length) {
return false;
}
var levelAvailable = available[level];
var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);
var tmsY = (yTiles - y - 1);
return isTileInRange(levelAvailable, x, tmsY);
}
};
return CesiumTerrainProvider;
});
/*global define*/
define('Core/EllipseGeometryLibrary',[
'./Cartesian3',
'./Math',
'./Matrix3',
'./Quaternion'
], function(
Cartesian3,
CesiumMath,
Matrix3,
Quaternion) {
'use strict';
var EllipseGeometryLibrary = {};
var rotAxis = new Cartesian3();
var tempVec = new Cartesian3();
var unitQuat = new Quaternion();
var rotMtx = new Matrix3();
function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) {
var azimuth = theta + rotation;
Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis);
Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec);
Cartesian3.add(rotAxis, tempVec, rotAxis);
var cosThetaSquared = Math.cos(theta);
cosThetaSquared = cosThetaSquared * cosThetaSquared;
var sinThetaSquared = Math.sin(theta);
sinThetaSquared = sinThetaSquared * sinThetaSquared;
var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared);
var angle = radius / mag;
// Create the quaternion to rotate the position vector to the boundary of the ellipse.
Quaternion.fromAxisAngle(rotAxis, angle, unitQuat);
Matrix3.fromQuaternion(unitQuat, rotMtx);
Matrix3.multiplyByVector(rotMtx, unitPos, result);
Cartesian3.normalize(result, result);
Cartesian3.multiplyByScalar(result, mag, result);
return result;
}
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchNormal = new Cartesian3();
/**
* Returns the positions raised to the given heights
* @private
*/
EllipseGeometryLibrary.raisePositionsToHeight = function(positions, options, extrude) {
var ellipsoid = options.ellipsoid;
var height = options.height;
var extrudedHeight = options.extrudedHeight;
var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3;
var finalPositions = new Float64Array(size * 3);
var length = positions.length;
var bottomOffset = (extrude) ? length : 0;
for (var i = 0; i < length; i += 3) {
var i1 = i + 1;
var i2 = i + 2;
var position = Cartesian3.fromArray(positions, i, scratchCartesian1);
ellipsoid.scaleToGeodeticSurface(position, position);
var extrudedPosition = Cartesian3.clone(position, scratchCartesian2);
var normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal);
var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian3);
Cartesian3.add(position, scaledNormal, position);
if (extrude) {
Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal);
Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition);
finalPositions[i + bottomOffset] = extrudedPosition.x;
finalPositions[i1 + bottomOffset] = extrudedPosition.y;
finalPositions[i2 + bottomOffset] = extrudedPosition.z;
}
finalPositions[i] = position.x;
finalPositions[i1] = position.y;
finalPositions[i2] = position.z;
}
return finalPositions;
};
var unitPosScratch = new Cartesian3();
var eastVecScratch = new Cartesian3();
var northVecScratch = new Cartesian3();
/**
* Returns an array of positions that make up the ellipse.
* @private
*/
EllipseGeometryLibrary.computeEllipsePositions = function(options, addFillPositions, addEdgePositions) {
var semiMinorAxis = options.semiMinorAxis;
var semiMajorAxis = options.semiMajorAxis;
var rotation = options.rotation;
var center = options.center;
// Computing the arc-length of the ellipse is too expensive to be practical. Estimating it using the
// arc length of the sphere is too inaccurate and creates sharp edges when either the semi-major or
// semi-minor axis is much bigger than the other. Instead, scale the angle delta to make
// the distance along the ellipse boundary more closely match the granularity.
var granularity = options.granularity * 8.0;
var aSqr = semiMinorAxis * semiMinorAxis;
var bSqr = semiMajorAxis * semiMajorAxis;
var ab = semiMajorAxis * semiMinorAxis;
var mag = Cartesian3.magnitude(center);
var unitPos = Cartesian3.normalize(center, unitPosScratch);
var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch);
eastVec = Cartesian3.normalize(eastVec, eastVec);
var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch);
// The number of points in the first quadrant
var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity);
var deltaTheta = CesiumMath.PI_OVER_TWO / (numPts - 1);
var theta = CesiumMath.PI_OVER_TWO - numPts * deltaTheta;
if (theta < 0.0) {
numPts -= Math.ceil(Math.abs(theta) / deltaTheta);
}
// If the number of points were three, the ellipse
// would be tessellated like below:
//
// *---*
// / | \ | \
// *---*---*---*
// / | \ | \ | \ | \
// / .*---*---*---*. \
// * ` | \ | \ | \ | `*
// \`.*---*---*---*.`/
// \ | \ | \ | \ | /
// *---*---*---*
// \ | \ | /
// *---*
// The first and last column have one position and fan to connect to the adjacent column.
// Each other vertical column contains an even number of positions.
var size = 2 * (numPts * (numPts + 2));
var positions = (addFillPositions) ? new Array(size * 3) : undefined;
var positionIndex = 0;
var position = scratchCartesian1;
var reflectedPosition = scratchCartesian2;
var outerPositionsLength = (numPts * 4) * 3;
var outerRightIndex = outerPositionsLength - 1;
var outerLeftIndex = 0;
var outerPositions = (addEdgePositions) ? new Array(outerPositionsLength) : undefined;
var i;
var j;
var numInterior;
var t;
var interiorPosition;
// Compute points in the 'eastern' half of the ellipse
theta = CesiumMath.PI_OVER_TWO;
position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
if (addFillPositions) {
positions[positionIndex++] = position.x;
positions[positionIndex++] = position.y;
positions[positionIndex++] = position.z;
}
if (addEdgePositions) {
outerPositions[outerRightIndex--] = position.z;
outerPositions[outerRightIndex--] = position.y;
outerPositions[outerRightIndex--] = position.x;
}
theta = CesiumMath.PI_OVER_TWO - deltaTheta;
for (i = 1; i < numPts + 1; ++i) {
position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
reflectedPosition = pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition);
if (addFillPositions) {
positions[positionIndex++] = position.x;
positions[positionIndex++] = position.y;
positions[positionIndex++] = position.z;
numInterior = 2 * i + 2;
for (j = 1; j < numInterior - 1; ++j) {
t = j / (numInterior - 1);
interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3);
positions[positionIndex++] = interiorPosition.x;
positions[positionIndex++] = interiorPosition.y;
positions[positionIndex++] = interiorPosition.z;
}
positions[positionIndex++] = reflectedPosition.x;
positions[positionIndex++] = reflectedPosition.y;
positions[positionIndex++] = reflectedPosition.z;
}
if (addEdgePositions) {
outerPositions[outerRightIndex--] = position.z;
outerPositions[outerRightIndex--] = position.y;
outerPositions[outerRightIndex--] = position.x;
outerPositions[outerLeftIndex++] = reflectedPosition.x;
outerPositions[outerLeftIndex++] = reflectedPosition.y;
outerPositions[outerLeftIndex++] = reflectedPosition.z;
}
theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta;
}
// Compute points in the 'western' half of the ellipse
for (i = numPts; i > 1; --i) {
theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta;
position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
reflectedPosition = pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition);
if (addFillPositions) {
positions[positionIndex++] = position.x;
positions[positionIndex++] = position.y;
positions[positionIndex++] = position.z;
numInterior = 2 * (i - 1) + 2;
for (j = 1; j < numInterior - 1; ++j) {
t = j / (numInterior - 1);
interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3);
positions[positionIndex++] = interiorPosition.x;
positions[positionIndex++] = interiorPosition.y;
positions[positionIndex++] = interiorPosition.z;
}
positions[positionIndex++] = reflectedPosition.x;
positions[positionIndex++] = reflectedPosition.y;
positions[positionIndex++] = reflectedPosition.z;
}
if (addEdgePositions) {
outerPositions[outerRightIndex--] = position.z;
outerPositions[outerRightIndex--] = position.y;
outerPositions[outerRightIndex--] = position.x;
outerPositions[outerLeftIndex++] = reflectedPosition.x;
outerPositions[outerLeftIndex++] = reflectedPosition.y;
outerPositions[outerLeftIndex++] = reflectedPosition.z;
}
}
theta = CesiumMath.PI_OVER_TWO;
position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
var r = {};
if (addFillPositions) {
positions[positionIndex++] = position.x;
positions[positionIndex++] = position.y;
positions[positionIndex++] = position.z;
r.positions = positions;
r.numPts = numPts;
}
if (addEdgePositions) {
outerPositions[outerRightIndex--] = position.z;
outerPositions[outerRightIndex--] = position.y;
outerPositions[outerRightIndex--] = position.x;
r.outerPositions = outerPositions;
}
return r;
};
return EllipseGeometryLibrary;
});
/*global define*/
define('Core/GeometryInstance',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./Matrix4'
], function(
defaultValue,
defined,
DeveloperError,
Matrix4) {
'use strict';
/**
* Geometry instancing allows one {@link Geometry} object to be positions in several
* different locations and colored uniquely. For example, one {@link BoxGeometry} can
* be instanced several times, each with a different modelMatrix
to change
* its position, rotation, and scale.
*
* @alias GeometryInstance
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Geometry} options.geometry The geometry to instance.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix that transforms to transform the geometry from model to world coordinates.
* @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick} or get/set per-instance attributes with {@link Primitive#getGeometryInstanceAttributes}.
* @param {Object} [options.attributes] Per-instance attributes like a show or color attribute shown in the example below.
*
*
* @example
* // Create geometry for a box, and two instances that refer to it.
* // One instance positions the box on the bottom and colored aqua.
* // The other instance positions the box on the top and color white.
* var geometry = Cesium.BoxGeometry.fromDimensions({
* vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL,
* dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 500000.0)
* });
* var instanceBottom = new Cesium.GeometryInstance({
* geometry : geometry,
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
* },
* id : 'bottom'
* });
* var instanceTop = new Cesium.GeometryInstance({
* geometry : geometry,
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 3000000.0), new Cesium.Matrix4()),
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
* },
* id : 'top'
* });
*
* @see Geometry
*/
function GeometryInstance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.geometry)) {
throw new DeveloperError('options.geometry is required.');
}
/**
* The geometry being instanced.
*
* @type Geometry
*
* @default undefined
*/
this.geometry = options.geometry;
/**
* The 4x4 transformation matrix that transforms the geometry from model to world coordinates.
* When this is the identity matrix, the geometry is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type Matrix4
*
* @default Matrix4.IDENTITY
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
/**
* User-defined object returned when the instance is picked or used to get/set per-instance attributes.
*
* @type Object
*
* @default undefined
*
* @see Scene#pick
* @see Primitive#getGeometryInstanceAttributes
*/
this.id = options.id;
/**
* Used for picking primitives that wrap geometry instances.
*
* @private
*/
this.pickPrimitive = options.pickPrimitive;
/**
* Per-instance attributes like {@link ColorGeometryInstanceAttribute} or {@link ShowGeometryInstanceAttribute}.
* {@link Geometry} attributes varying per vertex; these attributes are constant for the entire instance.
*
* @type Object
*
* @default undefined
*/
this.attributes = defaultValue(options.attributes, {});
/**
* @private
*/
this.westHemisphereGeometry = undefined;
/**
* @private
*/
this.eastHemisphereGeometry = undefined;
}
return GeometryInstance;
});
/*global define*/
define('Core/EncodedCartesian3',[
'./Cartesian3',
'./defined',
'./DeveloperError'
], function(
Cartesian3,
defined,
DeveloperError) {
'use strict';
/**
* A fixed-point encoding of a {@link Cartesian3} with 64-bit floating-point components, as two {@link Cartesian3}
* values that, when converted to 32-bit floating-point and added, approximate the original input.
*
* This is used to encode positions in vertex buffers for rendering without jittering artifacts
* as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
*
*
* @alias EncodedCartesian3
* @constructor
*
* @private
*/
function EncodedCartesian3() {
/**
* The high bits for each component. Bits 0 to 22 store the whole value. Bits 23 to 31 are not used.
*
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.high = Cartesian3.clone(Cartesian3.ZERO);
/**
* The low bits for each component. Bits 7 to 22 store the whole value, and bits 0 to 6 store the fraction. Bits 23 to 31 are not used.
*
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.low = Cartesian3.clone(Cartesian3.ZERO);
}
/**
* Encodes a 64-bit floating-point value as two floating-point values that, when converted to
* 32-bit floating-point and added, approximate the original input. The returned object
* has high
and low
properties for the high and low bits, respectively.
*
* The fixed-point encoding follows {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
*
*
* @param {Number} value The floating-point value to encode.
* @param {Object} [result] The object onto which to store the result.
* @returns {Object} The modified result parameter or a new instance if one was not provided.
*
* @example
* var value = 1234567.1234567;
* var splitValue = Cesium.EncodedCartesian3.encode(value);
*/
EncodedCartesian3.encode = function(value, result) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(result)) {
result = {
high : 0.0,
low : 0.0
};
}
var doubleHigh;
if (value >= 0.0) {
doubleHigh = Math.floor(value / 65536.0) * 65536.0;
result.high = doubleHigh;
result.low = value - doubleHigh;
} else {
doubleHigh = Math.floor(-value / 65536.0) * 65536.0;
result.high = -doubleHigh;
result.low = value + doubleHigh;
}
return result;
};
var scratchEncode = {
high : 0.0,
low : 0.0
};
/**
* Encodes a {@link Cartesian3} with 64-bit floating-point components as two {@link Cartesian3}
* values that, when converted to 32-bit floating-point and added, approximate the original input.
*
* The fixed-point encoding follows {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
*
*
* @param {Cartesian3} cartesian The cartesian to encode.
* @param {EncodedCartesian3} [result] The object onto which to store the result.
* @returns {EncodedCartesian3} The modified result parameter or a new EncodedCartesian3 instance if one was not provided.
*
* @example
* var cart = new Cesium.Cartesian3(-10000000.0, 0.0, 10000000.0);
* var encoded = Cesium.EncodedCartesian3.fromCartesian(cart);
*/
EncodedCartesian3.fromCartesian = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
result = new EncodedCartesian3();
}
var high = result.high;
var low = result.low;
EncodedCartesian3.encode(cartesian.x, scratchEncode);
high.x = scratchEncode.high;
low.x = scratchEncode.low;
EncodedCartesian3.encode(cartesian.y, scratchEncode);
high.y = scratchEncode.high;
low.y = scratchEncode.low;
EncodedCartesian3.encode(cartesian.z, scratchEncode);
high.z = scratchEncode.high;
low.z = scratchEncode.low;
return result;
};
var encodedP = new EncodedCartesian3();
/**
* Encodes the provided cartesian
, and writes it to an array with high
* components followed by low
components, i.e. [high.x, high.y, high.z, low.x, low.y, low.z]
.
*
* This is used to create interleaved high-precision position vertex attributes.
*
*
* @param {Cartesian3} cartesian The cartesian to encode.
* @param {Number[]} cartesianArray The array to write to.
* @param {Number} index The index into the array to start writing. Six elements will be written.
*
* @exception {DeveloperError} index must be a number greater than or equal to 0.
*
* @example
* var positions = [
* new Cesium.Cartesian3(),
* // ...
* ];
* var encodedPositions = new Float32Array(2 * 3 * positions.length);
* var j = 0;
* for (var i = 0; i < positions.length; ++i) {
* Cesium.EncodedCartesian3.writeElement(positions[i], encodedPositions, j);
* j += 6;
* }
*/
EncodedCartesian3.writeElements = function(cartesian, cartesianArray, index) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(cartesianArray)) {
throw new DeveloperError('cartesianArray is required');
}
if (typeof index !== 'number' || index < 0) {
throw new DeveloperError('index must be a number greater than or equal to 0.');
}
EncodedCartesian3.fromCartesian(cartesian, encodedP);
var high = encodedP.high;
var low = encodedP.low;
cartesianArray[index] = high.x;
cartesianArray[index + 1] = high.y;
cartesianArray[index + 2] = high.z;
cartesianArray[index + 3] = low.x;
cartesianArray[index + 4] = low.y;
cartesianArray[index + 5] = low.z;
};
return EncodedCartesian3;
});
/*global define*/
define('Core/Tipsify',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Encapsulates an algorithm to optimize triangles for the post
* vertex-shader cache. This is based on the 2007 SIGGRAPH paper
* 'Fast Triangle Reordering for Vertex Locality and Reduced Overdraw.'
* The runtime is linear but several passes are made.
*
* @exports Tipsify
*
* @see
* Fast Triangle Reordering for Vertex Locality and Reduced Overdraw
* by Sander, Nehab, and Barczak
*
* @private
*/
var Tipsify = {};
/**
* Calculates the average cache miss ratio (ACMR) for a given set of indices.
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
* in the vertex buffer that define the geometry's triangles.
* @param {Number} [options.maximumIndex] The maximum value of the elements in args.indices
.
* If not supplied, this value will be computed.
* @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
* @returns {Number} The average cache miss ratio (ACMR).
*
* @exception {DeveloperError} indices length must be a multiple of three.
* @exception {DeveloperError} cacheSize must be greater than two.
*
* @example
* var indices = [0, 1, 2, 3, 4, 5];
* var maxIndex = 5;
* var cacheSize = 3;
* var acmr = Cesium.Tipsify.calculateACMR({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
*/
Tipsify.calculateACMR = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var indices = options.indices;
var maximumIndex = options.maximumIndex;
var cacheSize = defaultValue(options.cacheSize, 24);
if (!defined(indices)) {
throw new DeveloperError('indices is required.');
}
var numIndices = indices.length;
if (numIndices < 3 || numIndices % 3 !== 0) {
throw new DeveloperError('indices length must be a multiple of three.');
}
if (maximumIndex <= 0) {
throw new DeveloperError('maximumIndex must be greater than zero.');
}
if (cacheSize < 3) {
throw new DeveloperError('cacheSize must be greater than two.');
}
// Compute the maximumIndex if not given
if (!defined(maximumIndex)) {
maximumIndex = 0;
var currentIndex = 0;
var intoIndices = indices[currentIndex];
while (currentIndex < numIndices) {
if (intoIndices > maximumIndex) {
maximumIndex = intoIndices;
}
++currentIndex;
intoIndices = indices[currentIndex];
}
}
// Vertex time stamps
var vertexTimeStamps = [];
for ( var i = 0; i < maximumIndex + 1; i++) {
vertexTimeStamps[i] = 0;
}
// Cache processing
var s = cacheSize + 1;
for ( var j = 0; j < numIndices; ++j) {
if ((s - vertexTimeStamps[indices[j]]) > cacheSize) {
vertexTimeStamps[indices[j]] = s;
++s;
}
}
return (s - cacheSize + 1) / (numIndices / 3);
};
/**
* Optimizes triangles for the post-vertex shader cache.
*
* @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
* in the vertex buffer that define the geometry's triangles.
* @param {Number} [options.maximumIndex] The maximum value of the elements in args.indices
.
* If not supplied, this value will be computed.
* @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
* @returns {Number[]} A list of the input indices in an optimized order.
*
* @exception {DeveloperError} indices length must be a multiple of three.
* @exception {DeveloperError} cacheSize must be greater than two.
*
* @example
* var indices = [0, 1, 2, 3, 4, 5];
* var maxIndex = 5;
* var cacheSize = 3;
* var reorderedIndices = Cesium.Tipsify.tipsify({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
*/
Tipsify.tipsify = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var indices = options.indices;
var maximumIndex = options.maximumIndex;
var cacheSize = defaultValue(options.cacheSize, 24);
var cursor;
function skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne) {
while (deadEnd.length >= 1) {
// while the stack is not empty
var d = deadEnd[deadEnd.length - 1]; // top of the stack
deadEnd.splice(deadEnd.length - 1, 1); // pop the stack
if (vertices[d].numLiveTriangles > 0) {
return d;
}
}
while (cursor < maximumIndexPlusOne) {
if (vertices[cursor].numLiveTriangles > 0) {
++cursor;
return cursor - 1;
}
++cursor;
}
return -1;
}
function getNextVertex(indices, cacheSize, oneRing, vertices, s, deadEnd, maximumIndexPlusOne) {
var n = -1;
var p;
var m = -1;
var itOneRing = 0;
while (itOneRing < oneRing.length) {
var index = oneRing[itOneRing];
if (vertices[index].numLiveTriangles) {
p = 0;
if ((s - vertices[index].timeStamp + (2 * vertices[index].numLiveTriangles)) <= cacheSize) {
p = s - vertices[index].timeStamp;
}
if ((p > m) || (m === -1)) {
m = p;
n = index;
}
}
++itOneRing;
}
if (n === -1) {
return skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne);
}
return n;
}
if (!defined(indices)) {
throw new DeveloperError('indices is required.');
}
var numIndices = indices.length;
if (numIndices < 3 || numIndices % 3 !== 0) {
throw new DeveloperError('indices length must be a multiple of three.');
}
if (maximumIndex <= 0) {
throw new DeveloperError('maximumIndex must be greater than zero.');
}
if (cacheSize < 3) {
throw new DeveloperError('cacheSize must be greater than two.');
}
// Determine maximum index
var maximumIndexPlusOne = 0;
var currentIndex = 0;
var intoIndices = indices[currentIndex];
var endIndex = numIndices;
if (defined(maximumIndex)) {
maximumIndexPlusOne = maximumIndex + 1;
} else {
while (currentIndex < endIndex) {
if (intoIndices > maximumIndexPlusOne) {
maximumIndexPlusOne = intoIndices;
}
++currentIndex;
intoIndices = indices[currentIndex];
}
if (maximumIndexPlusOne === -1) {
return 0;
}
++maximumIndexPlusOne;
}
// Vertices
var vertices = [];
for ( var i = 0; i < maximumIndexPlusOne; i++) {
vertices[i] = {
numLiveTriangles : 0,
timeStamp : 0,
vertexTriangles : []
};
}
currentIndex = 0;
var triangle = 0;
while (currentIndex < endIndex) {
vertices[indices[currentIndex]].vertexTriangles.push(triangle);
++(vertices[indices[currentIndex]]).numLiveTriangles;
vertices[indices[currentIndex + 1]].vertexTriangles.push(triangle);
++(vertices[indices[currentIndex + 1]]).numLiveTriangles;
vertices[indices[currentIndex + 2]].vertexTriangles.push(triangle);
++(vertices[indices[currentIndex + 2]]).numLiveTriangles;
++triangle;
currentIndex += 3;
}
// Starting index
var f = 0;
// Time Stamp
var s = cacheSize + 1;
cursor = 1;
// Process
var oneRing = [];
var deadEnd = []; //Stack
var vertex;
var intoVertices;
var currentOutputIndex = 0;
var outputIndices = [];
var numTriangles = numIndices / 3;
var triangleEmitted = [];
for (i = 0; i < numTriangles; i++) {
triangleEmitted[i] = false;
}
var index;
var limit;
while (f !== -1) {
oneRing = [];
intoVertices = vertices[f];
limit = intoVertices.vertexTriangles.length;
for ( var k = 0; k < limit; ++k) {
triangle = intoVertices.vertexTriangles[k];
if (!triangleEmitted[triangle]) {
triangleEmitted[triangle] = true;
currentIndex = triangle + triangle + triangle;
for ( var j = 0; j < 3; ++j) {
// Set this index as a possible next index
index = indices[currentIndex];
oneRing.push(index);
deadEnd.push(index);
// Output index
outputIndices[currentOutputIndex] = index;
++currentOutputIndex;
// Cache processing
vertex = vertices[index];
--vertex.numLiveTriangles;
if ((s - vertex.timeStamp) > cacheSize) {
vertex.timeStamp = s;
++s;
}
++currentIndex;
}
}
}
f = getNextVertex(indices, cacheSize, oneRing, vertices, s, deadEnd, maximumIndexPlusOne);
}
return outputIndices;
};
return Tipsify;
});
/*global define*/
define('Core/GeometryPipeline',[
'./AttributeCompression',
'./barycentricCoordinates',
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./Cartesian4',
'./Cartographic',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./EncodedCartesian3',
'./GeographicProjection',
'./Geometry',
'./GeometryAttribute',
'./GeometryType',
'./IndexDatatype',
'./Intersect',
'./IntersectionTests',
'./Math',
'./Matrix3',
'./Matrix4',
'./Plane',
'./PrimitiveType',
'./Tipsify'
], function(
AttributeCompression,
barycentricCoordinates,
BoundingSphere,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
EncodedCartesian3,
GeographicProjection,
Geometry,
GeometryAttribute,
GeometryType,
IndexDatatype,
Intersect,
IntersectionTests,
CesiumMath,
Matrix3,
Matrix4,
Plane,
PrimitiveType,
Tipsify) {
'use strict';
/**
* Content pipeline functions for geometries.
*
* @exports GeometryPipeline
*
* @see Geometry
*/
var GeometryPipeline = {};
function addTriangle(lines, index, i0, i1, i2) {
lines[index++] = i0;
lines[index++] = i1;
lines[index++] = i1;
lines[index++] = i2;
lines[index++] = i2;
lines[index] = i0;
}
function trianglesToLines(triangles) {
var count = triangles.length;
var size = (count / 3) * 6;
var lines = IndexDatatype.createTypedArray(count, size);
var index = 0;
for ( var i = 0; i < count; i += 3, index += 6) {
addTriangle(lines, index, triangles[i], triangles[i + 1], triangles[i + 2]);
}
return lines;
}
function triangleStripToLines(triangles) {
var count = triangles.length;
if (count >= 3) {
var size = (count - 2) * 6;
var lines = IndexDatatype.createTypedArray(count, size);
addTriangle(lines, 0, triangles[0], triangles[1], triangles[2]);
var index = 6;
for ( var i = 3; i < count; ++i, index += 6) {
addTriangle(lines, index, triangles[i - 1], triangles[i], triangles[i - 2]);
}
return lines;
}
return new Uint16Array();
}
function triangleFanToLines(triangles) {
if (triangles.length > 0) {
var count = triangles.length - 1;
var size = (count - 1) * 6;
var lines = IndexDatatype.createTypedArray(count, size);
var base = triangles[0];
var index = 0;
for ( var i = 1; i < count; ++i, index += 6) {
addTriangle(lines, index, base, triangles[i], triangles[i + 1]);
}
return lines;
}
return new Uint16Array();
}
/**
* Converts a geometry's triangle indices to line indices. If the geometry has an indices
* and its primitiveType
is TRIANGLES
, TRIANGLE_STRIP
,
* TRIANGLE_FAN
, it is converted to LINES
; otherwise, the geometry is not changed.
*
* This is commonly used to create a wireframe geometry for visual debugging.
*
*
* @param {Geometry} geometry The geometry to modify.
* @returns {Geometry} The modified geometry
argument, with its triangle indices converted to lines.
*
* @exception {DeveloperError} geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN.
*
* @example
* geometry = Cesium.GeometryPipeline.toWireframe(geometry);
*/
GeometryPipeline.toWireframe = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
var indices = geometry.indices;
if (defined(indices)) {
switch (geometry.primitiveType) {
case PrimitiveType.TRIANGLES:
geometry.indices = trianglesToLines(indices);
break;
case PrimitiveType.TRIANGLE_STRIP:
geometry.indices = triangleStripToLines(indices);
break;
case PrimitiveType.TRIANGLE_FAN:
geometry.indices = triangleFanToLines(indices);
break;
default:
throw new DeveloperError('geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN.');
}
geometry.primitiveType = PrimitiveType.LINES;
}
return geometry;
};
/**
* Creates a new {@link Geometry} with LINES
representing the provided
* attribute (attributeName
) for the provided geometry. This is used to
* visualize vector attributes like normals, binormals, and tangents.
*
* @param {Geometry} geometry The Geometry
instance with the attribute.
* @param {String} [attributeName='normal'] The name of the attribute.
* @param {Number} [length=10000.0] The length of each line segment in meters. This can be negative to point the vector in the opposite direction.
* @returns {Geometry} A new Geometry
instance with line segments for the vector.
*
* @exception {DeveloperError} geometry.attributes must have an attribute with the same name as the attributeName parameter.
*
* @example
* var geometry = Cesium.GeometryPipeline.createLineSegmentsForVectors(instance.geometry, 'binormal', 100000.0);
*/
GeometryPipeline.createLineSegmentsForVectors = function(geometry, attributeName, length) {
attributeName = defaultValue(attributeName, 'normal');
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
if (!defined(geometry.attributes.position)) {
throw new DeveloperError('geometry.attributes.position is required.');
}
if (!defined(geometry.attributes[attributeName])) {
throw new DeveloperError('geometry.attributes must have an attribute with the same name as the attributeName parameter, ' + attributeName + '.');
}
length = defaultValue(length, 10000.0);
var positions = geometry.attributes.position.values;
var vectors = geometry.attributes[attributeName].values;
var positionsLength = positions.length;
var newPositions = new Float64Array(2 * positionsLength);
var j = 0;
for (var i = 0; i < positionsLength; i += 3) {
newPositions[j++] = positions[i];
newPositions[j++] = positions[i + 1];
newPositions[j++] = positions[i + 2];
newPositions[j++] = positions[i] + (vectors[i] * length);
newPositions[j++] = positions[i + 1] + (vectors[i + 1] * length);
newPositions[j++] = positions[i + 2] + (vectors[i + 2] * length);
}
var newBoundingSphere;
var bs = geometry.boundingSphere;
if (defined(bs)) {
newBoundingSphere = new BoundingSphere(bs.center, bs.radius + length);
}
return new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : newPositions
})
},
primitiveType : PrimitiveType.LINES,
boundingSphere : newBoundingSphere
});
};
/**
* Creates an object that maps attribute names to unique locations (indices)
* for matching vertex attributes and shader programs.
*
* @param {Geometry} geometry The geometry, which is not modified, to create the object for.
* @returns {Object} An object with attribute name / index pairs.
*
* @example
* var attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry);
* // Example output
* // {
* // 'position' : 0,
* // 'normal' : 1
* // }
*/
GeometryPipeline.createAttributeLocations = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
// There can be a WebGL performance hit when attribute 0 is disabled, so
// assign attribute locations to well-known attributes.
var semantics = [
'position',
'positionHigh',
'positionLow',
// From VertexFormat.position - after 2D projection and high-precision encoding
'position3DHigh',
'position3DLow',
'position2DHigh',
'position2DLow',
// From Primitive
'pickColor',
// From VertexFormat
'normal',
'st',
'binormal',
'tangent',
// From compressing texture coordinates and normals
'compressedAttributes'
];
var attributes = geometry.attributes;
var indices = {};
var j = 0;
var i;
var len = semantics.length;
// Attribute locations for well-known attributes
for (i = 0; i < len; ++i) {
var semantic = semantics[i];
if (defined(attributes[semantic])) {
indices[semantic] = j++;
}
}
// Locations for custom attributes
for (var name in attributes) {
if (attributes.hasOwnProperty(name) && (!defined(indices[name]))) {
indices[name] = j++;
}
}
return indices;
};
/**
* Reorders a geometry's attributes and indices
to achieve better performance from the GPU's pre-vertex-shader cache.
*
* @param {Geometry} geometry The geometry to modify.
* @returns {Geometry} The modified geometry
argument, with its attributes and indices reordered for the GPU's pre-vertex-shader cache.
*
* @exception {DeveloperError} Each attribute array in geometry.attributes must have the same number of attributes.
*
*
* @example
* geometry = Cesium.GeometryPipeline.reorderForPreVertexCache(geometry);
*
* @see GeometryPipeline.reorderForPostVertexCache
*/
GeometryPipeline.reorderForPreVertexCache = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
var numVertices = Geometry.computeNumberOfVertices(geometry);
var indices = geometry.indices;
if (defined(indices)) {
var indexCrossReferenceOldToNew = new Int32Array(numVertices);
for ( var i = 0; i < numVertices; i++) {
indexCrossReferenceOldToNew[i] = -1;
}
// Construct cross reference and reorder indices
var indicesIn = indices;
var numIndices = indicesIn.length;
var indicesOut = IndexDatatype.createTypedArray(numVertices, numIndices);
var intoIndicesIn = 0;
var intoIndicesOut = 0;
var nextIndex = 0;
var tempIndex;
while (intoIndicesIn < numIndices) {
tempIndex = indexCrossReferenceOldToNew[indicesIn[intoIndicesIn]];
if (tempIndex !== -1) {
indicesOut[intoIndicesOut] = tempIndex;
} else {
tempIndex = indicesIn[intoIndicesIn];
indexCrossReferenceOldToNew[tempIndex] = nextIndex;
indicesOut[intoIndicesOut] = nextIndex;
++nextIndex;
}
++intoIndicesIn;
++intoIndicesOut;
}
geometry.indices = indicesOut;
// Reorder attributes
var attributes = geometry.attributes;
for ( var property in attributes) {
if (attributes.hasOwnProperty(property) &&
defined(attributes[property]) &&
defined(attributes[property].values)) {
var attribute = attributes[property];
var elementsIn = attribute.values;
var intoElementsIn = 0;
var numComponents = attribute.componentsPerAttribute;
var elementsOut = ComponentDatatype.createTypedArray(attribute.componentDatatype, nextIndex * numComponents);
while (intoElementsIn < numVertices) {
var temp = indexCrossReferenceOldToNew[intoElementsIn];
if (temp !== -1) {
for (i = 0; i < numComponents; i++) {
elementsOut[numComponents * temp + i] = elementsIn[numComponents * intoElementsIn + i];
}
}
++intoElementsIn;
}
attribute.values = elementsOut;
}
}
}
return geometry;
};
/**
* Reorders a geometry's indices
to achieve better performance from the GPU's
* post vertex-shader cache by using the Tipsify algorithm. If the geometry primitiveType
* is not TRIANGLES
or the geometry does not have an indices
, this function has no effect.
*
* @param {Geometry} geometry The geometry to modify.
* @param {Number} [cacheCapacity=24] The number of vertices that can be held in the GPU's vertex cache.
* @returns {Geometry} The modified geometry
argument, with its indices reordered for the post-vertex-shader cache.
*
* @exception {DeveloperError} cacheCapacity must be greater than two.
*
*
* @example
* geometry = Cesium.GeometryPipeline.reorderForPostVertexCache(geometry);
*
* @see GeometryPipeline.reorderForPreVertexCache
* @see {@link http://gfx.cs.princ0eton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf|Fast Triangle Reordering for Vertex Locality and Reduced Overdraw}
* by Sander, Nehab, and Barczak
*/
GeometryPipeline.reorderForPostVertexCache = function(geometry, cacheCapacity) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
var indices = geometry.indices;
if ((geometry.primitiveType === PrimitiveType.TRIANGLES) && (defined(indices))) {
var numIndices = indices.length;
var maximumIndex = 0;
for ( var j = 0; j < numIndices; j++) {
if (indices[j] > maximumIndex) {
maximumIndex = indices[j];
}
}
geometry.indices = Tipsify.tipsify({
indices : indices,
maximumIndex : maximumIndex,
cacheSize : cacheCapacity
});
}
return geometry;
};
function copyAttributesDescriptions(attributes) {
var newAttributes = {};
for ( var attribute in attributes) {
if (attributes.hasOwnProperty(attribute) &&
defined(attributes[attribute]) &&
defined(attributes[attribute].values)) {
var attr = attributes[attribute];
newAttributes[attribute] = new GeometryAttribute({
componentDatatype : attr.componentDatatype,
componentsPerAttribute : attr.componentsPerAttribute,
normalize : attr.normalize,
values : []
});
}
}
return newAttributes;
}
function copyVertex(destinationAttributes, sourceAttributes, index) {
for ( var attribute in sourceAttributes) {
if (sourceAttributes.hasOwnProperty(attribute) &&
defined(sourceAttributes[attribute]) &&
defined(sourceAttributes[attribute].values)) {
var attr = sourceAttributes[attribute];
for ( var k = 0; k < attr.componentsPerAttribute; ++k) {
destinationAttributes[attribute].values.push(attr.values[(index * attr.componentsPerAttribute) + k]);
}
}
}
}
/**
* Splits a geometry into multiple geometries, if necessary, to ensure that indices in the
* indices
fit into unsigned shorts. This is used to meet the WebGL requirements
* when unsigned int indices are not supported.
*
* If the geometry does not have any indices
, this function has no effect.
*
*
* @param {Geometry} geometry The geometry to be split into multiple geometries.
* @returns {Geometry[]} An array of geometries, each with indices that fit into unsigned shorts.
*
* @exception {DeveloperError} geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS
* @exception {DeveloperError} All geometry attribute lists must have the same number of attributes.
*
* @example
* var geometries = Cesium.GeometryPipeline.fitToUnsignedShortIndices(geometry);
*/
GeometryPipeline.fitToUnsignedShortIndices = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
if ((defined(geometry.indices)) &&
((geometry.primitiveType !== PrimitiveType.TRIANGLES) &&
(geometry.primitiveType !== PrimitiveType.LINES) &&
(geometry.primitiveType !== PrimitiveType.POINTS))) {
throw new DeveloperError('geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS.');
}
var geometries = [];
// If there's an index list and more than 64K attributes, it is possible that
// some indices are outside the range of unsigned short [0, 64K - 1]
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (defined(geometry.indices) && (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES)) {
var oldToNewIndex = [];
var newIndices = [];
var currentIndex = 0;
var newAttributes = copyAttributesDescriptions(geometry.attributes);
var originalIndices = geometry.indices;
var numberOfIndices = originalIndices.length;
var indicesPerPrimitive;
if (geometry.primitiveType === PrimitiveType.TRIANGLES) {
indicesPerPrimitive = 3;
} else if (geometry.primitiveType === PrimitiveType.LINES) {
indicesPerPrimitive = 2;
} else if (geometry.primitiveType === PrimitiveType.POINTS) {
indicesPerPrimitive = 1;
}
for ( var j = 0; j < numberOfIndices; j += indicesPerPrimitive) {
for (var k = 0; k < indicesPerPrimitive; ++k) {
var x = originalIndices[j + k];
var i = oldToNewIndex[x];
if (!defined(i)) {
i = currentIndex++;
oldToNewIndex[x] = i;
copyVertex(newAttributes, geometry.attributes, x);
}
newIndices.push(i);
}
if (currentIndex + indicesPerPrimitive >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
geometries.push(new Geometry({
attributes : newAttributes,
indices : newIndices,
primitiveType : geometry.primitiveType,
boundingSphere : geometry.boundingSphere,
boundingSphereCV : geometry.boundingSphereCV
}));
// Reset for next vertex-array
oldToNewIndex = [];
newIndices = [];
currentIndex = 0;
newAttributes = copyAttributesDescriptions(geometry.attributes);
}
}
if (newIndices.length !== 0) {
geometries.push(new Geometry({
attributes : newAttributes,
indices : newIndices,
primitiveType : geometry.primitiveType,
boundingSphere : geometry.boundingSphere,
boundingSphereCV : geometry.boundingSphereCV
}));
}
} else {
// No need to split into multiple geometries
geometries.push(geometry);
}
return geometries;
};
var scratchProjectTo2DCartesian3 = new Cartesian3();
var scratchProjectTo2DCartographic = new Cartographic();
/**
* Projects a geometry's 3D position
attribute to 2D, replacing the position
* attribute with separate position3D
and position2D
attributes.
*
* If the geometry does not have a position
, this function has no effect.
*
*
* @param {Geometry} geometry The geometry to modify.
* @param {String} attributeName The name of the attribute.
* @param {String} attributeName3D The name of the attribute in 3D.
* @param {String} attributeName2D The name of the attribute in 2D.
* @param {Object} [projection=new GeographicProjection()] The projection to use.
* @returns {Geometry} The modified geometry
argument with position3D
and position2D
attributes.
*
* @exception {DeveloperError} geometry must have attribute matching the attributeName argument.
* @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE.
* @exception {DeveloperError} Could not project a point to 2D.
*
* @example
* geometry = Cesium.GeometryPipeline.projectTo2D(geometry, 'position', 'position3D', 'position2D');
*/
GeometryPipeline.projectTo2D = function(geometry, attributeName, attributeName3D, attributeName2D, projection) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
if (!defined(attributeName)) {
throw new DeveloperError('attributeName is required.');
}
if (!defined(attributeName3D)) {
throw new DeveloperError('attributeName3D is required.');
}
if (!defined(attributeName2D)) {
throw new DeveloperError('attributeName2D is required.');
}
if (!defined(geometry.attributes[attributeName])) {
throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.');
}
if (geometry.attributes[attributeName].componentDatatype !== ComponentDatatype.DOUBLE) {
throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.');
}
var attribute = geometry.attributes[attributeName];
projection = (defined(projection)) ? projection : new GeographicProjection();
var ellipsoid = projection.ellipsoid;
// Project original values to 2D.
var values3D = attribute.values;
var projectedValues = new Float64Array(values3D.length);
var index = 0;
for ( var i = 0; i < values3D.length; i += 3) {
var value = Cartesian3.fromArray(values3D, i, scratchProjectTo2DCartesian3);
var lonLat = ellipsoid.cartesianToCartographic(value, scratchProjectTo2DCartographic);
if (!defined(lonLat)) {
throw new DeveloperError('Could not project point (' + value.x + ', ' + value.y + ', ' + value.z + ') to 2D.');
}
var projectedLonLat = projection.project(lonLat, scratchProjectTo2DCartesian3);
projectedValues[index++] = projectedLonLat.x;
projectedValues[index++] = projectedLonLat.y;
projectedValues[index++] = projectedLonLat.z;
}
// Rename original cartesians to WGS84 cartesians.
geometry.attributes[attributeName3D] = attribute;
// Replace original cartesians with 2D projected cartesians
geometry.attributes[attributeName2D] = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : projectedValues
});
delete geometry.attributes[attributeName];
return geometry;
};
var encodedResult = {
high : 0.0,
low : 0.0
};
/**
* Encodes floating-point geometry attribute values as two separate attributes to improve
* rendering precision.
*
* This is commonly used to create high-precision position vertex attributes.
*
*
* @param {Geometry} geometry The geometry to modify.
* @param {String} attributeName The name of the attribute.
* @param {String} attributeHighName The name of the attribute for the encoded high bits.
* @param {String} attributeLowName The name of the attribute for the encoded low bits.
* @returns {Geometry} The modified geometry
argument, with its encoded attribute.
*
* @exception {DeveloperError} geometry must have attribute matching the attributeName argument.
* @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE.
*
* @example
* geometry = Cesium.GeometryPipeline.encodeAttribute(geometry, 'position3D', 'position3DHigh', 'position3DLow');
*/
GeometryPipeline.encodeAttribute = function(geometry, attributeName, attributeHighName, attributeLowName) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
if (!defined(attributeName)) {
throw new DeveloperError('attributeName is required.');
}
if (!defined(attributeHighName)) {
throw new DeveloperError('attributeHighName is required.');
}
if (!defined(attributeLowName)) {
throw new DeveloperError('attributeLowName is required.');
}
if (!defined(geometry.attributes[attributeName])) {
throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.');
}
if (geometry.attributes[attributeName].componentDatatype !== ComponentDatatype.DOUBLE) {
throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.');
}
var attribute = geometry.attributes[attributeName];
var values = attribute.values;
var length = values.length;
var highValues = new Float32Array(length);
var lowValues = new Float32Array(length);
for (var i = 0; i < length; ++i) {
EncodedCartesian3.encode(values[i], encodedResult);
highValues[i] = encodedResult.high;
lowValues[i] = encodedResult.low;
}
var componentsPerAttribute = attribute.componentsPerAttribute;
geometry.attributes[attributeHighName] = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : componentsPerAttribute,
values : highValues
});
geometry.attributes[attributeLowName] = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : componentsPerAttribute,
values : lowValues
});
delete geometry.attributes[attributeName];
return geometry;
};
var scratchCartesian3 = new Cartesian3();
function transformPoint(matrix, attribute) {
if (defined(attribute)) {
var values = attribute.values;
var length = values.length;
for (var i = 0; i < length; i += 3) {
Cartesian3.unpack(values, i, scratchCartesian3);
Matrix4.multiplyByPoint(matrix, scratchCartesian3, scratchCartesian3);
Cartesian3.pack(scratchCartesian3, values, i);
}
}
}
function transformVector(matrix, attribute) {
if (defined(attribute)) {
var values = attribute.values;
var length = values.length;
for (var i = 0; i < length; i += 3) {
Cartesian3.unpack(values, i, scratchCartesian3);
Matrix3.multiplyByVector(matrix, scratchCartesian3, scratchCartesian3);
scratchCartesian3 = Cartesian3.normalize(scratchCartesian3, scratchCartesian3);
Cartesian3.pack(scratchCartesian3, values, i);
}
}
}
var inverseTranspose = new Matrix4();
var normalMatrix = new Matrix3();
/**
* Transforms a geometry instance to world coordinates. This changes
* the instance's modelMatrix
to {@link Matrix4.IDENTITY} and transforms the
* following attributes if they are present: position
, normal
,
* binormal
, and tangent
.
*
* @param {GeometryInstance} instance The geometry instance to modify.
* @returns {GeometryInstance} The modified instance
argument, with its attributes transforms to world coordinates.
*
* @example
* Cesium.GeometryPipeline.transformToWorldCoordinates(instance);
*/
GeometryPipeline.transformToWorldCoordinates = function(instance) {
if (!defined(instance)) {
throw new DeveloperError('instance is required.');
}
var modelMatrix = instance.modelMatrix;
if (Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) {
// Already in world coordinates
return instance;
}
var attributes = instance.geometry.attributes;
// Transform attributes in known vertex formats
transformPoint(modelMatrix, attributes.position);
transformPoint(modelMatrix, attributes.prevPosition);
transformPoint(modelMatrix, attributes.nextPosition);
if ((defined(attributes.normal)) ||
(defined(attributes.binormal)) ||
(defined(attributes.tangent))) {
Matrix4.inverse(modelMatrix, inverseTranspose);
Matrix4.transpose(inverseTranspose, inverseTranspose);
Matrix4.getRotation(inverseTranspose, normalMatrix);
transformVector(normalMatrix, attributes.normal);
transformVector(normalMatrix, attributes.binormal);
transformVector(normalMatrix, attributes.tangent);
}
var boundingSphere = instance.geometry.boundingSphere;
if (defined(boundingSphere)) {
instance.geometry.boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, boundingSphere);
}
instance.modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
return instance;
};
function findAttributesInAllGeometries(instances, propertyName) {
var length = instances.length;
var attributesInAllGeometries = {};
var attributes0 = instances[0][propertyName].attributes;
var name;
for (name in attributes0) {
if (attributes0.hasOwnProperty(name) &&
defined(attributes0[name]) &&
defined(attributes0[name].values)) {
var attribute = attributes0[name];
var numberOfComponents = attribute.values.length;
var inAllGeometries = true;
// Does this same attribute exist in all geometries?
for (var i = 1; i < length; ++i) {
var otherAttribute = instances[i][propertyName].attributes[name];
if ((!defined(otherAttribute)) ||
(attribute.componentDatatype !== otherAttribute.componentDatatype) ||
(attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) ||
(attribute.normalize !== otherAttribute.normalize)) {
inAllGeometries = false;
break;
}
numberOfComponents += otherAttribute.values.length;
}
if (inAllGeometries) {
attributesInAllGeometries[name] = new GeometryAttribute({
componentDatatype : attribute.componentDatatype,
componentsPerAttribute : attribute.componentsPerAttribute,
normalize : attribute.normalize,
values : ComponentDatatype.createTypedArray(attribute.componentDatatype, numberOfComponents)
});
}
}
}
return attributesInAllGeometries;
}
var tempScratch = new Cartesian3();
function combineGeometries(instances, propertyName) {
var length = instances.length;
var name;
var i;
var j;
var k;
var m = instances[0].modelMatrix;
var haveIndices = (defined(instances[0][propertyName].indices));
var primitiveType = instances[0][propertyName].primitiveType;
for (i = 1; i < length; ++i) {
if (!Matrix4.equals(instances[i].modelMatrix, m)) {
throw new DeveloperError('All instances must have the same modelMatrix.');
}
if ((defined(instances[i][propertyName].indices)) !== haveIndices) {
throw new DeveloperError('All instance geometries must have an indices or not have one.');
}
if (instances[i][propertyName].primitiveType !== primitiveType) {
throw new DeveloperError('All instance geometries must have the same primitiveType.');
}
}
// Find subset of attributes in all geometries
var attributes = findAttributesInAllGeometries(instances, propertyName);
var values;
var sourceValues;
var sourceValuesLength;
// Combine attributes from each geometry into a single typed array
for (name in attributes) {
if (attributes.hasOwnProperty(name)) {
values = attributes[name].values;
k = 0;
for (i = 0; i < length; ++i) {
sourceValues = instances[i][propertyName].attributes[name].values;
sourceValuesLength = sourceValues.length;
for (j = 0; j < sourceValuesLength; ++j) {
values[k++] = sourceValues[j];
}
}
}
}
// Combine index lists
var indices;
if (haveIndices) {
var numberOfIndices = 0;
for (i = 0; i < length; ++i) {
numberOfIndices += instances[i][propertyName].indices.length;
}
var numberOfVertices = Geometry.computeNumberOfVertices(new Geometry({
attributes : attributes,
primitiveType : PrimitiveType.POINTS
}));
var destIndices = IndexDatatype.createTypedArray(numberOfVertices, numberOfIndices);
var destOffset = 0;
var offset = 0;
for (i = 0; i < length; ++i) {
var sourceIndices = instances[i][propertyName].indices;
var sourceIndicesLen = sourceIndices.length;
for (k = 0; k < sourceIndicesLen; ++k) {
destIndices[destOffset++] = offset + sourceIndices[k];
}
offset += Geometry.computeNumberOfVertices(instances[i][propertyName]);
}
indices = destIndices;
}
// Create bounding sphere that includes all instances
var center = new Cartesian3();
var radius = 0.0;
var bs;
for (i = 0; i < length; ++i) {
bs = instances[i][propertyName].boundingSphere;
if (!defined(bs)) {
// If any geometries have an undefined bounding sphere, then so does the combined geometry
center = undefined;
break;
}
Cartesian3.add(bs.center, center, center);
}
if (defined(center)) {
Cartesian3.divideByScalar(center, length, center);
for (i = 0; i < length; ++i) {
bs = instances[i][propertyName].boundingSphere;
var tempRadius = Cartesian3.magnitude(Cartesian3.subtract(bs.center, center, tempScratch)) + bs.radius;
if (tempRadius > radius) {
radius = tempRadius;
}
}
}
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : primitiveType,
boundingSphere : (defined(center)) ? new BoundingSphere(center, radius) : undefined
});
}
/**
* Combines geometry from several {@link GeometryInstance} objects into one geometry.
* This concatenates the attributes, concatenates and adjusts the indices, and creates
* a bounding sphere encompassing all instances.
*
* If the instances do not have the same attributes, a subset of attributes common
* to all instances is used, and the others are ignored.
*
*
* This is used by {@link Primitive} to efficiently render a large amount of static data.
*
*
* @private
*
* @param {GeometryInstance[]} [instances] The array of {@link GeometryInstance} objects whose geometry will be combined.
* @returns {Geometry} A single geometry created from the provided geometry instances.
*
* @exception {DeveloperError} All instances must have the same modelMatrix.
* @exception {DeveloperError} All instance geometries must have an indices or not have one.
* @exception {DeveloperError} All instance geometries must have the same primitiveType.
*
*
* @example
* for (var i = 0; i < instances.length; ++i) {
* Cesium.GeometryPipeline.transformToWorldCoordinates(instances[i]);
* }
* var geometries = Cesium.GeometryPipeline.combineInstances(instances);
*
* @see GeometryPipeline.transformToWorldCoordinates
*/
GeometryPipeline.combineInstances = function(instances) {
if ((!defined(instances)) || (instances.length < 1)) {
throw new DeveloperError('instances is required and must have length greater than zero.');
}
var instanceGeometry = [];
var instanceSplitGeometry = [];
var length = instances.length;
for (var i = 0; i < length; ++i) {
var instance = instances[i];
if (defined(instance.geometry)) {
instanceGeometry.push(instance);
} else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) {
instanceSplitGeometry.push(instance);
}
}
var geometries = [];
if (instanceGeometry.length > 0) {
geometries.push(combineGeometries(instanceGeometry, 'geometry'));
}
if (instanceSplitGeometry.length > 0) {
geometries.push(combineGeometries(instanceSplitGeometry, 'westHemisphereGeometry'));
geometries.push(combineGeometries(instanceSplitGeometry, 'eastHemisphereGeometry'));
}
return geometries;
};
var normal = new Cartesian3();
var v0 = new Cartesian3();
var v1 = new Cartesian3();
var v2 = new Cartesian3();
/**
* Computes per-vertex normals for a geometry containing TRIANGLES
by averaging the normals of
* all triangles incident to the vertex. The result is a new normal
attribute added to the geometry.
* This assumes a counter-clockwise winding order.
*
* @param {Geometry} geometry The geometry to modify.
* @returns {Geometry} The modified geometry
argument with the computed normal
attribute.
*
* @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3.
* @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}.
*
* @example
* Cesium.GeometryPipeline.computeNormal(geometry);
*/
GeometryPipeline.computeNormal = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
if (!defined(geometry.attributes.position) || !defined(geometry.attributes.position.values)) {
throw new DeveloperError('geometry.attributes.position.values is required.');
}
if (!defined(geometry.indices)) {
throw new DeveloperError('geometry.indices is required.');
}
if (geometry.indices.length < 2 || geometry.indices.length % 3 !== 0) {
throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.');
}
if (geometry.primitiveType !== PrimitiveType.TRIANGLES) {
throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.');
}
var indices = geometry.indices;
var attributes = geometry.attributes;
var vertices = attributes.position.values;
var numVertices = attributes.position.values.length / 3;
var numIndices = indices.length;
var normalsPerVertex = new Array(numVertices);
var normalsPerTriangle = new Array(numIndices / 3);
var normalIndices = new Array(numIndices);
for ( var i = 0; i < numVertices; i++) {
normalsPerVertex[i] = {
indexOffset : 0,
count : 0,
currentCount : 0
};
}
var j = 0;
for (i = 0; i < numIndices; i += 3) {
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var i03 = i0 * 3;
var i13 = i1 * 3;
var i23 = i2 * 3;
v0.x = vertices[i03];
v0.y = vertices[i03 + 1];
v0.z = vertices[i03 + 2];
v1.x = vertices[i13];
v1.y = vertices[i13 + 1];
v1.z = vertices[i13 + 2];
v2.x = vertices[i23];
v2.y = vertices[i23 + 1];
v2.z = vertices[i23 + 2];
normalsPerVertex[i0].count++;
normalsPerVertex[i1].count++;
normalsPerVertex[i2].count++;
Cartesian3.subtract(v1, v0, v1);
Cartesian3.subtract(v2, v0, v2);
normalsPerTriangle[j] = Cartesian3.cross(v1, v2, new Cartesian3());
j++;
}
var indexOffset = 0;
for (i = 0; i < numVertices; i++) {
normalsPerVertex[i].indexOffset += indexOffset;
indexOffset += normalsPerVertex[i].count;
}
j = 0;
var vertexNormalData;
for (i = 0; i < numIndices; i += 3) {
vertexNormalData = normalsPerVertex[indices[i]];
var index = vertexNormalData.indexOffset + vertexNormalData.currentCount;
normalIndices[index] = j;
vertexNormalData.currentCount++;
vertexNormalData = normalsPerVertex[indices[i + 1]];
index = vertexNormalData.indexOffset + vertexNormalData.currentCount;
normalIndices[index] = j;
vertexNormalData.currentCount++;
vertexNormalData = normalsPerVertex[indices[i + 2]];
index = vertexNormalData.indexOffset + vertexNormalData.currentCount;
normalIndices[index] = j;
vertexNormalData.currentCount++;
j++;
}
var normalValues = new Float32Array(numVertices * 3);
for (i = 0; i < numVertices; i++) {
var i3 = i * 3;
vertexNormalData = normalsPerVertex[i];
if (vertexNormalData.count > 0) {
Cartesian3.clone(Cartesian3.ZERO, normal);
for (j = 0; j < vertexNormalData.count; j++) {
Cartesian3.add(normal, normalsPerTriangle[normalIndices[vertexNormalData.indexOffset + j]], normal);
}
Cartesian3.normalize(normal, normal);
normalValues[i3] = normal.x;
normalValues[i3 + 1] = normal.y;
normalValues[i3 + 2] = normal.z;
} else {
normalValues[i3] = 0.0;
normalValues[i3 + 1] = 0.0;
normalValues[i3 + 2] = 1.0;
}
}
geometry.attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normalValues
});
return geometry;
};
var normalScratch = new Cartesian3();
var normalScale = new Cartesian3();
var tScratch = new Cartesian3();
/**
* Computes per-vertex binormals and tangents for a geometry containing TRIANGLES
.
* The result is new binormal
and tangent
attributes added to the geometry.
* This assumes a counter-clockwise winding order.
*
* Based on Computing Tangent Space Basis Vectors
* for an Arbitrary Mesh by Eric Lengyel.
*
*
* @param {Geometry} geometry The geometry to modify.
* @returns {Geometry} The modified geometry
argument with the computed binormal
and tangent
attributes.
*
* @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3.
* @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}.
*
* @example
* Cesium.GeometryPipeline.computeBinormalAndTangent(geometry);
*/
GeometryPipeline.computeBinormalAndTangent = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
var attributes = geometry.attributes;
var indices = geometry.indices;
if (!defined(attributes.position) || !defined(attributes.position.values)) {
throw new DeveloperError('geometry.attributes.position.values is required.');
}
if (!defined(attributes.normal) || !defined(attributes.normal.values)) {
throw new DeveloperError('geometry.attributes.normal.values is required.');
}
if (!defined(attributes.st) || !defined(attributes.st.values)) {
throw new DeveloperError('geometry.attributes.st.values is required.');
}
if (!defined(indices)) {
throw new DeveloperError('geometry.indices is required.');
}
if (indices.length < 2 || indices.length % 3 !== 0) {
throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.');
}
if (geometry.primitiveType !== PrimitiveType.TRIANGLES) {
throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.');
}
var vertices = geometry.attributes.position.values;
var normals = geometry.attributes.normal.values;
var st = geometry.attributes.st.values;
var numVertices = geometry.attributes.position.values.length / 3;
var numIndices = indices.length;
var tan1 = new Array(numVertices * 3);
for ( var i = 0; i < tan1.length; i++) {
tan1[i] = 0;
}
var i03;
var i13;
var i23;
for (i = 0; i < numIndices; i += 3) {
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
i03 = i0 * 3;
i13 = i1 * 3;
i23 = i2 * 3;
var i02 = i0 * 2;
var i12 = i1 * 2;
var i22 = i2 * 2;
var ux = vertices[i03];
var uy = vertices[i03 + 1];
var uz = vertices[i03 + 2];
var wx = st[i02];
var wy = st[i02 + 1];
var t1 = st[i12 + 1] - wy;
var t2 = st[i22 + 1] - wy;
var r = 1.0 / ((st[i12] - wx) * t2 - (st[i22] - wx) * t1);
var sdirx = (t2 * (vertices[i13] - ux) - t1 * (vertices[i23] - ux)) * r;
var sdiry = (t2 * (vertices[i13 + 1] - uy) - t1 * (vertices[i23 + 1] - uy)) * r;
var sdirz = (t2 * (vertices[i13 + 2] - uz) - t1 * (vertices[i23 + 2] - uz)) * r;
tan1[i03] += sdirx;
tan1[i03 + 1] += sdiry;
tan1[i03 + 2] += sdirz;
tan1[i13] += sdirx;
tan1[i13 + 1] += sdiry;
tan1[i13 + 2] += sdirz;
tan1[i23] += sdirx;
tan1[i23 + 1] += sdiry;
tan1[i23 + 2] += sdirz;
}
var binormalValues = new Float32Array(numVertices * 3);
var tangentValues = new Float32Array(numVertices * 3);
for (i = 0; i < numVertices; i++) {
i03 = i * 3;
i13 = i03 + 1;
i23 = i03 + 2;
var n = Cartesian3.fromArray(normals, i03, normalScratch);
var t = Cartesian3.fromArray(tan1, i03, tScratch);
var scalar = Cartesian3.dot(n, t);
Cartesian3.multiplyByScalar(n, scalar, normalScale);
Cartesian3.normalize(Cartesian3.subtract(t, normalScale, t), t);
tangentValues[i03] = t.x;
tangentValues[i13] = t.y;
tangentValues[i23] = t.z;
Cartesian3.normalize(Cartesian3.cross(n, t, t), t);
binormalValues[i03] = t.x;
binormalValues[i13] = t.y;
binormalValues[i23] = t.z;
}
geometry.attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangentValues
});
geometry.attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormalValues
});
return geometry;
};
var scratchCartesian2 = new Cartesian2();
var toEncode1 = new Cartesian3();
var toEncode2 = new Cartesian3();
var toEncode3 = new Cartesian3();
/**
* Compresses and packs geometry normal attribute values to save memory.
*
* @param {Geometry} geometry The geometry to modify.
* @returns {Geometry} The modified geometry
argument, with its normals compressed and packed.
*
* @example
* geometry = Cesium.GeometryPipeline.compressVertices(geometry);
*/
GeometryPipeline.compressVertices = function(geometry) {
if (!defined(geometry)) {
throw new DeveloperError('geometry is required.');
}
var normalAttribute = geometry.attributes.normal;
var stAttribute = geometry.attributes.st;
if (!defined(normalAttribute) && !defined(stAttribute)) {
return geometry;
}
var tangentAttribute = geometry.attributes.tangent;
var binormalAttribute = geometry.attributes.binormal;
var normals;
var st;
var tangents;
var binormals;
if (defined(normalAttribute)) {
normals = normalAttribute.values;
}
if (defined(stAttribute)) {
st = stAttribute.values;
}
if (defined(tangentAttribute)) {
tangents = tangentAttribute.values;
}
if (binormalAttribute) {
binormals = binormalAttribute.values;
}
var length = defined(normals) ? normals.length : st.length;
var numComponents = defined(normals) ? 3.0 : 2.0;
var numVertices = length / numComponents;
var compressedLength = numVertices;
var numCompressedComponents = defined(st) && defined(normals) ? 2.0 : 1.0;
numCompressedComponents += defined(tangents) || defined(binormals) ? 1.0 : 0.0;
compressedLength *= numCompressedComponents;
var compressedAttributes = new Float32Array(compressedLength);
var normalIndex = 0;
for (var i = 0; i < numVertices; ++i) {
if (defined(st)) {
Cartesian2.fromArray(st, i * 2.0, scratchCartesian2);
compressedAttributes[normalIndex++] = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
}
var index = i * 3.0;
if (defined(normals) && defined(tangents) && defined(binormals)) {
Cartesian3.fromArray(normals, index, toEncode1);
Cartesian3.fromArray(tangents, index, toEncode2);
Cartesian3.fromArray(binormals, index, toEncode3);
AttributeCompression.octPack(toEncode1, toEncode2, toEncode3, scratchCartesian2);
compressedAttributes[normalIndex++] = scratchCartesian2.x;
compressedAttributes[normalIndex++] = scratchCartesian2.y;
} else {
if (defined(normals)) {
Cartesian3.fromArray(normals, index, toEncode1);
compressedAttributes[normalIndex++] = AttributeCompression.octEncodeFloat(toEncode1);
}
if (defined(tangents)) {
Cartesian3.fromArray(tangents, index, toEncode1);
compressedAttributes[normalIndex++] = AttributeCompression.octEncodeFloat(toEncode1);
}
if (defined(binormals)) {
Cartesian3.fromArray(binormals, index, toEncode1);
compressedAttributes[normalIndex++] = AttributeCompression.octEncodeFloat(toEncode1);
}
}
}
geometry.attributes.compressedAttributes = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : numCompressedComponents,
values : compressedAttributes
});
if (defined(normals)) {
delete geometry.attributes.normal;
}
if (defined(st)) {
delete geometry.attributes.st;
}
if (defined(tangents)) {
delete geometry.attributes.tangent;
}
if (defined(binormals)) {
delete geometry.attributes.binormal;
}
return geometry;
};
function indexTriangles(geometry) {
if (defined(geometry.indices)) {
return geometry;
}
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (numberOfVertices < 3) {
throw new DeveloperError('The number of vertices must be at least three.');
}
if (numberOfVertices % 3 !== 0) {
throw new DeveloperError('The number of vertices must be a multiple of three.');
}
var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices);
for (var i = 0; i < numberOfVertices; ++i) {
indices[i] = i;
}
geometry.indices = indices;
return geometry;
}
function indexTriangleFan(geometry) {
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (numberOfVertices < 3) {
throw new DeveloperError('The number of vertices must be at least three.');
}
var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 2) * 3);
indices[0] = 1;
indices[1] = 0;
indices[2] = 2;
var indicesIndex = 3;
for (var i = 3; i < numberOfVertices; ++i) {
indices[indicesIndex++] = i - 1;
indices[indicesIndex++] = 0;
indices[indicesIndex++] = i;
}
geometry.indices = indices;
geometry.primitiveType = PrimitiveType.TRIANGLES;
return geometry;
}
function indexTriangleStrip(geometry) {
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (numberOfVertices < 3) {
throw new DeveloperError('The number of vertices must be at least 3.');
}
var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 2) * 3);
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
if (numberOfVertices > 3) {
indices[3] = 0;
indices[4] = 2;
indices[5] = 3;
}
var indicesIndex = 6;
for (var i = 3; i < numberOfVertices - 1; i += 2) {
indices[indicesIndex++] = i;
indices[indicesIndex++] = i - 1;
indices[indicesIndex++] = i + 1;
if (i + 2 < numberOfVertices) {
indices[indicesIndex++] = i;
indices[indicesIndex++] = i + 1;
indices[indicesIndex++] = i + 2;
}
}
geometry.indices = indices;
geometry.primitiveType = PrimitiveType.TRIANGLES;
return geometry;
}
function indexLines(geometry) {
if (defined(geometry.indices)) {
return geometry;
}
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (numberOfVertices < 2) {
throw new DeveloperError('The number of vertices must be at least two.');
}
if (numberOfVertices % 2 !== 0) {
throw new DeveloperError('The number of vertices must be a multiple of 2.');
}
var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices);
for (var i = 0; i < numberOfVertices; ++i) {
indices[i] = i;
}
geometry.indices = indices;
return geometry;
}
function indexLineStrip(geometry) {
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (numberOfVertices < 2) {
throw new DeveloperError('The number of vertices must be at least two.');
}
var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 1) * 2);
indices[0] = 0;
indices[1] = 1;
var indicesIndex = 2;
for (var i = 2; i < numberOfVertices; ++i) {
indices[indicesIndex++] = i - 1;
indices[indicesIndex++] = i;
}
geometry.indices = indices;
geometry.primitiveType = PrimitiveType.LINES;
return geometry;
}
function indexLineLoop(geometry) {
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
if (numberOfVertices < 2) {
throw new DeveloperError('The number of vertices must be at least two.');
}
var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices * 2);
indices[0] = 0;
indices[1] = 1;
var indicesIndex = 2;
for (var i = 2; i < numberOfVertices; ++i) {
indices[indicesIndex++] = i - 1;
indices[indicesIndex++] = i;
}
indices[indicesIndex++] = numberOfVertices - 1;
indices[indicesIndex] = 0;
geometry.indices = indices;
geometry.primitiveType = PrimitiveType.LINES;
return geometry;
}
function indexPrimitive(geometry) {
switch (geometry.primitiveType) {
case PrimitiveType.TRIANGLE_FAN:
return indexTriangleFan(geometry);
case PrimitiveType.TRIANGLE_STRIP:
return indexTriangleStrip(geometry);
case PrimitiveType.TRIANGLES:
return indexTriangles(geometry);
case PrimitiveType.LINE_STRIP:
return indexLineStrip(geometry);
case PrimitiveType.LINE_LOOP:
return indexLineLoop(geometry);
case PrimitiveType.LINES:
return indexLines(geometry);
}
return geometry;
}
function offsetPointFromXZPlane(p, isBehind) {
if (Math.abs(p.y) < CesiumMath.EPSILON6){
if (isBehind) {
p.y = -CesiumMath.EPSILON6;
} else {
p.y = CesiumMath.EPSILON6;
}
}
}
function offsetTriangleFromXZPlane(p0, p1, p2) {
if (p0.y !== 0.0 && p1.y !== 0.0 && p2.y !== 0.0) {
offsetPointFromXZPlane(p0, p0.y < 0.0);
offsetPointFromXZPlane(p1, p1.y < 0.0);
offsetPointFromXZPlane(p2, p2.y < 0.0);
return;
}
var p0y = Math.abs(p0.y);
var p1y = Math.abs(p1.y);
var p2y = Math.abs(p2.y);
var sign;
if (p0y > p1y) {
if (p0y > p2y) {
sign = CesiumMath.sign(p0.y);
} else {
sign = CesiumMath.sign(p2.y);
}
} else if (p1y > p2y) {
sign = CesiumMath.sign(p1.y);
} else {
sign = CesiumMath.sign(p2.y);
}
var isBehind = sign < 0.0;
offsetPointFromXZPlane(p0, isBehind);
offsetPointFromXZPlane(p1, isBehind);
offsetPointFromXZPlane(p2, isBehind);
}
var c3 = new Cartesian3();
function getXZIntersectionOffsetPoints(p, p1, u1, v1) {
Cartesian3.add(p, Cartesian3.multiplyByScalar(Cartesian3.subtract(p1, p, c3), p.y/(p.y-p1.y), c3), u1);
Cartesian3.clone(u1, v1);
offsetPointFromXZPlane(u1, true);
offsetPointFromXZPlane(v1, false);
}
var u1 = new Cartesian3();
var u2 = new Cartesian3();
var q1 = new Cartesian3();
var q2 = new Cartesian3();
var splitTriangleResult = {
positions : new Array(7),
indices : new Array(3 * 3)
};
function splitTriangle(p0, p1, p2) {
// In WGS84 coordinates, for a triangle approximately on the
// ellipsoid to cross the IDL, first it needs to be on the
// negative side of the plane x = 0.
if ((p0.x >= 0.0) || (p1.x >= 0.0) || (p2.x >= 0.0)) {
return undefined;
}
offsetTriangleFromXZPlane(p0, p1, p2);
var p0Behind = p0.y < 0.0;
var p1Behind = p1.y < 0.0;
var p2Behind = p2.y < 0.0;
var numBehind = 0;
numBehind += p0Behind ? 1 : 0;
numBehind += p1Behind ? 1 : 0;
numBehind += p2Behind ? 1 : 0;
var indices = splitTriangleResult.indices;
if (numBehind === 1) {
indices[1] = 3;
indices[2] = 4;
indices[5] = 6;
indices[7] = 6;
indices[8] = 5;
if (p0Behind) {
getXZIntersectionOffsetPoints(p0, p1, u1, q1);
getXZIntersectionOffsetPoints(p0, p2, u2, q2);
indices[0] = 0;
indices[3] = 1;
indices[4] = 2;
indices[6] = 1;
} else if (p1Behind) {
getXZIntersectionOffsetPoints(p1, p2, u1, q1);
getXZIntersectionOffsetPoints(p1, p0, u2, q2);
indices[0] = 1;
indices[3] = 2;
indices[4] = 0;
indices[6] = 2;
} else if (p2Behind) {
getXZIntersectionOffsetPoints(p2, p0, u1, q1);
getXZIntersectionOffsetPoints(p2, p1, u2, q2);
indices[0] = 2;
indices[3] = 0;
indices[4] = 1;
indices[6] = 0;
}
} else if (numBehind === 2) {
indices[2] = 4;
indices[4] = 4;
indices[5] = 3;
indices[7] = 5;
indices[8] = 6;
if (!p0Behind) {
getXZIntersectionOffsetPoints(p0, p1, u1, q1);
getXZIntersectionOffsetPoints(p0, p2, u2, q2);
indices[0] = 1;
indices[1] = 2;
indices[3] = 1;
indices[6] = 0;
} else if (!p1Behind) {
getXZIntersectionOffsetPoints(p1, p2, u1, q1);
getXZIntersectionOffsetPoints(p1, p0, u2, q2);
indices[0] = 2;
indices[1] = 0;
indices[3] = 2;
indices[6] = 1;
} else if (!p2Behind) {
getXZIntersectionOffsetPoints(p2, p0, u1, q1);
getXZIntersectionOffsetPoints(p2, p1, u2, q2);
indices[0] = 0;
indices[1] = 1;
indices[3] = 0;
indices[6] = 2;
}
}
var positions = splitTriangleResult.positions;
positions[0] = p0;
positions[1] = p1;
positions[2] = p2;
positions.length = 3;
if (numBehind === 1 || numBehind === 2) {
positions[3] = u1;
positions[4] = u2;
positions[5] = q1;
positions[6] = q2;
positions.length = 7;
}
return splitTriangleResult;
}
function updateGeometryAfterSplit(geometry, computeBoundingSphere) {
var attributes = geometry.attributes;
if (attributes.position.values.length === 0) {
return undefined;
}
for (var property in attributes) {
if (attributes.hasOwnProperty(property) &&
defined(attributes[property]) &&
defined(attributes[property].values)) {
var attribute = attributes[property];
attribute.values = ComponentDatatype.createTypedArray(attribute.componentDatatype, attribute.values);
}
}
var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
geometry.indices = IndexDatatype.createTypedArray(numberOfVertices, geometry.indices);
if (computeBoundingSphere) {
geometry.boundingSphere = BoundingSphere.fromVertices(attributes.position.values);
}
return geometry;
}
function copyGeometryForSplit(geometry) {
var attributes = geometry.attributes;
var copiedAttributes = {};
for (var property in attributes) {
if (attributes.hasOwnProperty(property) &&
defined(attributes[property]) &&
defined(attributes[property].values)) {
var attribute = attributes[property];
copiedAttributes[property] = new GeometryAttribute({
componentDatatype : attribute.componentDatatype,
componentsPerAttribute : attribute.componentsPerAttribute,
normalize : attribute.normalize,
values : []
});
}
}
return new Geometry({
attributes : copiedAttributes,
indices : [],
primitiveType : geometry.primitiveType
});
}
function updateInstanceAfterSplit(instance, westGeometry, eastGeometry) {
var computeBoundingSphere = defined(instance.geometry.boundingSphere);
westGeometry = updateGeometryAfterSplit(westGeometry, computeBoundingSphere);
eastGeometry = updateGeometryAfterSplit(eastGeometry, computeBoundingSphere);
if (defined(eastGeometry) && !defined(westGeometry)) {
instance.geometry = eastGeometry;
} else if (!defined(eastGeometry) && defined(westGeometry)) {
instance.geometry = westGeometry;
} else {
instance.westHemisphereGeometry = westGeometry;
instance.eastHemisphereGeometry = eastGeometry;
instance.geometry = undefined;
}
}
var p0Scratch = new Cartesian3();
var p1Scratch = new Cartesian3();
var p2Scratch = new Cartesian3();
var barycentricScratch = new Cartesian3();
var s0Scratch = new Cartesian2();
var s1Scratch = new Cartesian2();
var s2Scratch = new Cartesian2();
function computeTriangleAttributes(i0, i1, i2, point, positions, normals, binormals, tangents, texCoords, currentAttributes, insertedIndex) {
if (!defined(normals) && !defined(binormals) && !defined(tangents) && !defined(texCoords)) {
return;
}
var p0 = Cartesian3.fromArray(positions, i0 * 3, p0Scratch);
var p1 = Cartesian3.fromArray(positions, i1 * 3, p1Scratch);
var p2 = Cartesian3.fromArray(positions, i2 * 3, p2Scratch);
var coords = barycentricCoordinates(point, p0, p1, p2, barycentricScratch);
if (defined(normals)) {
var n0 = Cartesian3.fromArray(normals, i0 * 3, p0Scratch);
var n1 = Cartesian3.fromArray(normals, i1 * 3, p1Scratch);
var n2 = Cartesian3.fromArray(normals, i2 * 3, p2Scratch);
Cartesian3.multiplyByScalar(n0, coords.x, n0);
Cartesian3.multiplyByScalar(n1, coords.y, n1);
Cartesian3.multiplyByScalar(n2, coords.z, n2);
var normal = Cartesian3.add(n0, n1, n0);
Cartesian3.add(normal, n2, normal);
Cartesian3.normalize(normal, normal);
Cartesian3.pack(normal, currentAttributes.normal.values, insertedIndex * 3);
}
if (defined(binormals)) {
var b0 = Cartesian3.fromArray(binormals, i0 * 3, p0Scratch);
var b1 = Cartesian3.fromArray(binormals, i1 * 3, p1Scratch);
var b2 = Cartesian3.fromArray(binormals, i2 * 3, p2Scratch);
Cartesian3.multiplyByScalar(b0, coords.x, b0);
Cartesian3.multiplyByScalar(b1, coords.y, b1);
Cartesian3.multiplyByScalar(b2, coords.z, b2);
var binormal = Cartesian3.add(b0, b1, b0);
Cartesian3.add(binormal, b2, binormal);
Cartesian3.normalize(binormal, binormal);
Cartesian3.pack(binormal, currentAttributes.binormal.values, insertedIndex * 3);
}
if (defined(tangents)) {
var t0 = Cartesian3.fromArray(tangents, i0 * 3, p0Scratch);
var t1 = Cartesian3.fromArray(tangents, i1 * 3, p1Scratch);
var t2 = Cartesian3.fromArray(tangents, i2 * 3, p2Scratch);
Cartesian3.multiplyByScalar(t0, coords.x, t0);
Cartesian3.multiplyByScalar(t1, coords.y, t1);
Cartesian3.multiplyByScalar(t2, coords.z, t2);
var tangent = Cartesian3.add(t0, t1, t0);
Cartesian3.add(tangent, t2, tangent);
Cartesian3.normalize(tangent, tangent);
Cartesian3.pack(tangent, currentAttributes.tangent.values, insertedIndex * 3);
}
if (defined(texCoords)) {
var s0 = Cartesian2.fromArray(texCoords, i0 * 2, s0Scratch);
var s1 = Cartesian2.fromArray(texCoords, i1 * 2, s1Scratch);
var s2 = Cartesian2.fromArray(texCoords, i2 * 2, s2Scratch);
Cartesian2.multiplyByScalar(s0, coords.x, s0);
Cartesian2.multiplyByScalar(s1, coords.y, s1);
Cartesian2.multiplyByScalar(s2, coords.z, s2);
var texCoord = Cartesian2.add(s0, s1, s0);
Cartesian2.add(texCoord, s2, texCoord);
Cartesian2.pack(texCoord, currentAttributes.st.values, insertedIndex * 2);
}
}
function insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, currentIndex, point) {
var insertIndex = currentAttributes.position.values.length / 3;
if (currentIndex !== -1) {
var prevIndex = indices[currentIndex];
var newIndex = currentIndexMap[prevIndex];
if (newIndex === -1) {
currentIndexMap[prevIndex] = insertIndex;
currentAttributes.position.values.push(point.x, point.y, point.z);
currentIndices.push(insertIndex);
return insertIndex;
}
currentIndices.push(newIndex);
return newIndex;
}
currentAttributes.position.values.push(point.x, point.y, point.z);
currentIndices.push(insertIndex);
return insertIndex;
}
function splitLongitudeTriangles(instance) {
var geometry = instance.geometry;
var attributes = geometry.attributes;
var positions = attributes.position.values;
var normals = (defined(attributes.normal)) ? attributes.normal.values : undefined;
var binormals = (defined(attributes.binormal)) ? attributes.binormal.values : undefined;
var tangents = (defined(attributes.tangent)) ? attributes.tangent.values : undefined;
var texCoords = (defined(attributes.st)) ? attributes.st.values : undefined;
var indices = geometry.indices;
var eastGeometry = copyGeometryForSplit(geometry);
var westGeometry = copyGeometryForSplit(geometry);
var currentAttributes;
var currentIndices;
var currentIndexMap;
var insertedIndex;
var i;
var westGeometryIndexMap = [];
westGeometryIndexMap.length = positions.length / 3;
var eastGeometryIndexMap = [];
eastGeometryIndexMap.length = positions.length / 3;
for (i = 0; i < westGeometryIndexMap.length; ++i) {
westGeometryIndexMap[i] = -1;
eastGeometryIndexMap[i] = -1;
}
var len = indices.length;
for (i = 0; i < len; i += 3) {
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var p0 = Cartesian3.fromArray(positions, i0 * 3);
var p1 = Cartesian3.fromArray(positions, i1 * 3);
var p2 = Cartesian3.fromArray(positions, i2 * 3);
var result = splitTriangle(p0, p1, p2);
if (defined(result) && result.positions.length > 3) {
var resultPositions = result.positions;
var resultIndices = result.indices;
var resultLength = resultIndices.length;
for (var j = 0; j < resultLength; ++j) {
var resultIndex = resultIndices[j];
var point = resultPositions[resultIndex];
if (point.y < 0.0) {
currentAttributes = westGeometry.attributes;
currentIndices = westGeometry.indices;
currentIndexMap = westGeometryIndexMap;
} else {
currentAttributes = eastGeometry.attributes;
currentIndices = eastGeometry.indices;
currentIndexMap = eastGeometryIndexMap;
}
insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, resultIndex < 3 ? i + resultIndex : -1, point);
computeTriangleAttributes(i0, i1, i2, point, positions, normals, binormals, tangents, texCoords, currentAttributes, insertedIndex);
}
} else {
if (defined(result)) {
p0 = result.positions[0];
p1 = result.positions[1];
p2 = result.positions[2];
}
if (p0.y < 0.0) {
currentAttributes = westGeometry.attributes;
currentIndices = westGeometry.indices;
currentIndexMap = westGeometryIndexMap;
} else {
currentAttributes = eastGeometry.attributes;
currentIndices = eastGeometry.indices;
currentIndexMap = eastGeometryIndexMap;
}
insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i, p0);
computeTriangleAttributes(i0, i1, i2, p0, positions, normals, binormals, tangents, texCoords, currentAttributes, insertedIndex);
insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i + 1, p1);
computeTriangleAttributes(i0, i1, i2, p1, positions, normals, binormals, tangents, texCoords, currentAttributes, insertedIndex);
insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i + 2, p2);
computeTriangleAttributes(i0, i1, i2, p2, positions, normals, binormals, tangents, texCoords, currentAttributes, insertedIndex);
}
}
updateInstanceAfterSplit(instance, westGeometry, eastGeometry);
}
var xzPlane = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Y);
var offsetScratch = new Cartesian3();
var offsetPointScratch = new Cartesian3();
function splitLongitudeLines(instance) {
var geometry = instance.geometry;
var attributes = geometry.attributes;
var positions = attributes.position.values;
var indices = geometry.indices;
var eastGeometry = copyGeometryForSplit(geometry);
var westGeometry = copyGeometryForSplit(geometry);
var i;
var length = indices.length;
var westGeometryIndexMap = [];
westGeometryIndexMap.length = positions.length / 3;
var eastGeometryIndexMap = [];
eastGeometryIndexMap.length = positions.length / 3;
for (i = 0; i < westGeometryIndexMap.length; ++i) {
westGeometryIndexMap[i] = -1;
eastGeometryIndexMap[i] = -1;
}
for (i = 0; i < length; i += 2) {
var i0 = indices[i];
var i1 = indices[i + 1];
var p0 = Cartesian3.fromArray(positions, i0 * 3, p0Scratch);
var p1 = Cartesian3.fromArray(positions, i1 * 3, p1Scratch);
if (Math.abs(p0.y) < CesiumMath.EPSILON6){
if (p0.y < 0.0) {
p0.y = -CesiumMath.EPSILON6;
} else {
p0.y = CesiumMath.EPSILON6;
}
}
if (Math.abs(p1.y) < CesiumMath.EPSILON6){
if (p1.y < 0.0) {
p1.y = -CesiumMath.EPSILON6;
} else {
p1.y = CesiumMath.EPSILON6;
}
}
var p0Attributes = eastGeometry.attributes;
var p0Indices = eastGeometry.indices;
var p0IndexMap = eastGeometryIndexMap;
var p1Attributes = westGeometry.attributes;
var p1Indices = westGeometry.indices;
var p1IndexMap = westGeometryIndexMap;
var intersection = IntersectionTests.lineSegmentPlane(p0, p1, xzPlane, p2Scratch);
if (defined(intersection)) {
// move point on the xz-plane slightly away from the plane
var offset = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Y, 5.0 * CesiumMath.EPSILON9, offsetScratch);
if (p0.y < 0.0) {
Cartesian3.negate(offset, offset);
p0Attributes = westGeometry.attributes;
p0Indices = westGeometry.indices;
p0IndexMap = westGeometryIndexMap;
p1Attributes = eastGeometry.attributes;
p1Indices = eastGeometry.indices;
p1IndexMap = eastGeometryIndexMap;
}
var offsetPoint = Cartesian3.add(intersection, offset, offsetPointScratch);
insertSplitPoint(p0Attributes, p0Indices, p0IndexMap, indices, i, p0);
insertSplitPoint(p0Attributes, p0Indices, p0IndexMap, indices, -1, offsetPoint);
Cartesian3.negate(offset, offset);
Cartesian3.add(intersection, offset, offsetPoint);
insertSplitPoint(p1Attributes, p1Indices, p1IndexMap, indices, -1, offsetPoint);
insertSplitPoint(p1Attributes, p1Indices, p1IndexMap, indices, i + 1, p1);
} else {
var currentAttributes;
var currentIndices;
var currentIndexMap;
if (p0.y < 0.0) {
currentAttributes = westGeometry.attributes;
currentIndices = westGeometry.indices;
currentIndexMap = westGeometryIndexMap;
} else {
currentAttributes = eastGeometry.attributes;
currentIndices = eastGeometry.indices;
currentIndexMap = eastGeometryIndexMap;
}
insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i, p0);
insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i + 1, p1);
}
}
updateInstanceAfterSplit(instance, westGeometry, eastGeometry);
}
var cartesian2Scratch0 = new Cartesian2();
var cartesian2Scratch1 = new Cartesian2();
var cartesian3Scratch0 = new Cartesian3();
var cartesian3Scratch2 = new Cartesian3();
var cartesian3Scratch3 = new Cartesian3();
var cartesian3Scratch4 = new Cartesian3();
var cartesian3Scratch5 = new Cartesian3();
var cartesian3Scratch6 = new Cartesian3();
var cartesian4Scratch0 = new Cartesian4();
function updateAdjacencyAfterSplit(geometry) {
var attributes = geometry.attributes;
var positions = attributes.position.values;
var prevPositions = attributes.prevPosition.values;
var nextPositions = attributes.nextPosition.values;
var length = positions.length;
for (var j = 0; j < length; j += 3) {
var position = Cartesian3.unpack(positions, j, cartesian3Scratch0);
if (position.x > 0.0) {
continue;
}
var prevPosition = Cartesian3.unpack(prevPositions, j, cartesian3Scratch2);
if ((position.y < 0.0 && prevPosition.y > 0.0) || (position.y > 0.0 && prevPosition.y < 0.0)) {
if (j - 3 > 0) {
prevPositions[j] = positions[j - 3];
prevPositions[j + 1] = positions[j - 2];
prevPositions[j + 2] = positions[j - 1];
} else {
Cartesian3.pack(position, prevPositions, j);
}
}
var nextPosition = Cartesian3.unpack(nextPositions, j, cartesian3Scratch3);
if ((position.y < 0.0 && nextPosition.y > 0.0) || (position.y > 0.0 && nextPosition.y < 0.0)) {
if (j + 3 < length) {
nextPositions[j] = positions[j + 3];
nextPositions[j + 1] = positions[j + 4];
nextPositions[j + 2] = positions[j + 5];
} else {
Cartesian3.pack(position, nextPositions, j);
}
}
}
}
var offsetScalar = 5.0 * CesiumMath.EPSILON9;
var coplanarOffset = CesiumMath.EPSILON6;
function splitLongitudePolyline(instance) {
var geometry = instance.geometry;
var attributes = geometry.attributes;
var positions = attributes.position.values;
var prevPositions = attributes.prevPosition.values;
var nextPositions = attributes.nextPosition.values;
var expandAndWidths = attributes.expandAndWidth.values;
var texCoords = (defined(attributes.st)) ? attributes.st.values : undefined;
var colors = (defined(attributes.color)) ? attributes.color.values : undefined;
var eastGeometry = copyGeometryForSplit(geometry);
var westGeometry = copyGeometryForSplit(geometry);
var i;
var j;
var index;
var intersectionFound = false;
var length = positions.length / 3;
for (i = 0; i < length; i += 4) {
var i0 = i;
var i2 = i + 2;
var p0 = Cartesian3.fromArray(positions, i0 * 3, cartesian3Scratch0);
var p2 = Cartesian3.fromArray(positions, i2 * 3, cartesian3Scratch2);
// Offset points that are close to the 180 longitude and change the previous/next point
// to be the same offset point so it can be projected to 2D. There is special handling in the
// shader for when position == prevPosition || position == nextPosition.
if (Math.abs(p0.y) < coplanarOffset) {
p0.y = coplanarOffset * (p2.y < 0.0 ? -1.0 : 1.0);
positions[i * 3 + 1] = p0.y;
positions[(i + 1) * 3 + 1] = p0.y;
for (j = i0 * 3; j < i0 * 3 + 4 * 3; j += 3) {
prevPositions[j] = positions[i * 3];
prevPositions[j + 1] = positions[i * 3 + 1];
prevPositions[j + 2] = positions[i * 3 + 2];
}
}
// Do the same but for when the line crosses 180 longitude in the opposite direction.
if (Math.abs(p2.y) < coplanarOffset) {
p2.y = coplanarOffset * (p0.y < 0.0 ? -1.0 : 1.0);
positions[(i + 2) * 3 + 1] = p2.y;
positions[(i + 3) * 3 + 1] = p2.y;
for (j = i0 * 3; j < i0 * 3 + 4 * 3; j += 3) {
nextPositions[j] = positions[(i + 2) * 3];
nextPositions[j + 1] = positions[(i + 2) * 3 + 1];
nextPositions[j + 2] = positions[(i + 2) * 3 + 2];
}
}
var p0Attributes = eastGeometry.attributes;
var p0Indices = eastGeometry.indices;
var p2Attributes = westGeometry.attributes;
var p2Indices = westGeometry.indices;
var intersection = IntersectionTests.lineSegmentPlane(p0, p2, xzPlane, cartesian3Scratch4);
if (defined(intersection)) {
intersectionFound = true;
// move point on the xz-plane slightly away from the plane
var offset = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Y, offsetScalar, cartesian3Scratch5);
if (p0.y < 0.0) {
Cartesian3.negate(offset, offset);
p0Attributes = westGeometry.attributes;
p0Indices = westGeometry.indices;
p2Attributes = eastGeometry.attributes;
p2Indices = eastGeometry.indices;
}
var offsetPoint = Cartesian3.add(intersection, offset, cartesian3Scratch6);
p0Attributes.position.values.push(p0.x, p0.y, p0.z, p0.x, p0.y, p0.z);
p0Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p0Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p0Attributes.prevPosition.values.push(prevPositions[i0 * 3], prevPositions[i0 * 3 + 1], prevPositions[i0 * 3 + 2]);
p0Attributes.prevPosition.values.push(prevPositions[i0 * 3 + 3], prevPositions[i0 * 3 + 4], prevPositions[i0 * 3 + 5]);
p0Attributes.prevPosition.values.push(p0.x, p0.y, p0.z, p0.x, p0.y, p0.z);
p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
Cartesian3.negate(offset, offset);
Cartesian3.add(intersection, offset, offsetPoint);
p2Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p2Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p2Attributes.position.values.push(p2.x, p2.y, p2.z, p2.x, p2.y, p2.z);
p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
p2Attributes.nextPosition.values.push(p2.x, p2.y, p2.z, p2.x, p2.y, p2.z);
p2Attributes.nextPosition.values.push(nextPositions[i2 * 3], nextPositions[i2 * 3 + 1], nextPositions[i2 * 3 + 2]);
p2Attributes.nextPosition.values.push(nextPositions[i2 * 3 + 3], nextPositions[i2 * 3 + 4], nextPositions[i2 * 3 + 5]);
var ew0 = Cartesian2.fromArray(expandAndWidths, i0 * 2, cartesian2Scratch0);
var width = Math.abs(ew0.y);
p0Attributes.expandAndWidth.values.push(-1, width, 1, width);
p0Attributes.expandAndWidth.values.push(-1, -width, 1, -width);
p2Attributes.expandAndWidth.values.push(-1, width, 1, width);
p2Attributes.expandAndWidth.values.push(-1, -width, 1, -width);
var t = Cartesian3.magnitudeSquared(Cartesian3.subtract(intersection, p0, cartesian3Scratch3));
t /= Cartesian3.magnitudeSquared(Cartesian3.subtract(p2, p0, cartesian3Scratch3));
if (defined(colors)) {
var c0 = Cartesian4.fromArray(colors, i0 * 4, cartesian4Scratch0);
var c2 = Cartesian4.fromArray(colors, i2 * 4, cartesian4Scratch0);
var r = CesiumMath.lerp(c0.x, c2.x, t);
var g = CesiumMath.lerp(c0.y, c2.y, t);
var b = CesiumMath.lerp(c0.z, c2.z, t);
var a = CesiumMath.lerp(c0.w, c2.w, t);
for (j = i0 * 4; j < i0 * 4 + 2 * 4; ++j) {
p0Attributes.color.values.push(colors[j]);
}
p0Attributes.color.values.push(r, g, b, a);
p0Attributes.color.values.push(r, g, b, a);
p2Attributes.color.values.push(r, g, b, a);
p2Attributes.color.values.push(r, g, b, a);
for (j = i2 * 4; j < i2 * 4 + 2 * 4; ++j) {
p2Attributes.color.values.push(colors[j]);
}
}
if (defined(texCoords)) {
var s0 = Cartesian2.fromArray(texCoords, i0 * 2, cartesian2Scratch0);
var s3 = Cartesian2.fromArray(texCoords, (i + 3) * 2, cartesian2Scratch1);
var sx = CesiumMath.lerp(s0.x, s3.x, t);
for (j = i0 * 2; j < i0 * 2 + 2 * 2; ++j) {
p0Attributes.st.values.push(texCoords[j]);
}
p0Attributes.st.values.push(sx, s0.y);
p0Attributes.st.values.push(sx, s3.y);
p2Attributes.st.values.push(sx, s0.y);
p2Attributes.st.values.push(sx, s3.y);
for (j = i2 * 2; j < i2 * 2 + 2 * 2; ++j) {
p2Attributes.st.values.push(texCoords[j]);
}
}
index = p0Attributes.position.values.length / 3 - 4;
p0Indices.push(index, index + 2, index + 1);
p0Indices.push(index + 1, index + 2, index + 3);
index = p2Attributes.position.values.length / 3 - 4;
p2Indices.push(index, index + 2, index + 1);
p2Indices.push(index + 1, index + 2, index + 3);
} else {
var currentAttributes;
var currentIndices;
if (p0.y < 0.0) {
currentAttributes = westGeometry.attributes;
currentIndices = westGeometry.indices;
} else {
currentAttributes = eastGeometry.attributes;
currentIndices = eastGeometry.indices;
}
currentAttributes.position.values.push(p0.x, p0.y, p0.z);
currentAttributes.position.values.push(p0.x, p0.y, p0.z);
currentAttributes.position.values.push(p2.x, p2.y, p2.z);
currentAttributes.position.values.push(p2.x, p2.y, p2.z);
for (j = i * 3; j < i * 3 + 4 * 3; ++j) {
currentAttributes.prevPosition.values.push(prevPositions[j]);
currentAttributes.nextPosition.values.push(nextPositions[j]);
}
for (j = i * 2; j < i * 2 + 4 * 2; ++j) {
currentAttributes.expandAndWidth.values.push(expandAndWidths[j]);
if (defined(texCoords)) {
currentAttributes.st.values.push(texCoords[j]);
}
}
if (defined(colors)) {
for (j = i * 4; j < i * 4 + 4 * 4; ++j) {
currentAttributes.color.values.push(colors[j]);
}
}
index = currentAttributes.position.values.length / 3 - 4;
currentIndices.push(index, index + 2, index + 1);
currentIndices.push(index + 1, index + 2, index + 3);
}
}
if (intersectionFound) {
updateAdjacencyAfterSplit(westGeometry);
updateAdjacencyAfterSplit(eastGeometry);
}
updateInstanceAfterSplit(instance, westGeometry, eastGeometry);
}
/**
* Splits the instances's geometry, by introducing new vertices and indices,that
* intersect the International Date Line and Prime Meridian so that no primitives cross longitude
* -180/180 degrees. This is not required for 3D drawing, but is required for
* correcting drawing in 2D and Columbus view.
*
* @private
*
* @param {GeometryInstance} instance The instance to modify.
* @returns {GeometryInstance} The modified instance
argument, with it's geometry split at the International Date Line.
*
* @example
* instance = Cesium.GeometryPipeline.splitLongitude(instance);
*/
GeometryPipeline.splitLongitude = function(instance) {
if (!defined(instance)) {
throw new DeveloperError('instance is required.');
}
var geometry = instance.geometry;
var boundingSphere = geometry.boundingSphere;
if (defined(boundingSphere)) {
var minX = boundingSphere.center.x - boundingSphere.radius;
if (minX > 0 || BoundingSphere.intersectPlane(boundingSphere, Plane.ORIGIN_ZX_PLANE) !== Intersect.INTERSECTING) {
return instance;
}
}
if (geometry.geometryType !== GeometryType.NONE) {
switch (geometry.geometryType) {
case GeometryType.POLYLINES:
splitLongitudePolyline(instance);
break;
case GeometryType.TRIANGLES:
splitLongitudeTriangles(instance);
break;
case GeometryType.LINES:
splitLongitudeLines(instance);
break;
}
} else {
indexPrimitive(geometry);
if (geometry.primitiveType === PrimitiveType.TRIANGLES) {
splitLongitudeTriangles(instance);
} else if (geometry.primitiveType === PrimitiveType.LINES) {
splitLongitudeLines(instance);
}
}
return instance;
};
return GeometryPipeline;
});
/*global define*/
define('Core/EllipseGeometry',[
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./Cartographic',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./EllipseGeometryLibrary',
'./Ellipsoid',
'./GeographicProjection',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./GeometryInstance',
'./GeometryPipeline',
'./IndexDatatype',
'./Math',
'./Matrix3',
'./Matrix4',
'./PrimitiveType',
'./Quaternion',
'./Rectangle',
'./Transforms',
'./VertexFormat'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartographic,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
DeveloperError,
EllipseGeometryLibrary,
Ellipsoid,
GeographicProjection,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryInstance,
GeometryPipeline,
IndexDatatype,
CesiumMath,
Matrix3,
Matrix4,
PrimitiveType,
Quaternion,
Rectangle,
Transforms,
VertexFormat) {
'use strict';
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4 = new Cartesian3();
var texCoordScratch = new Cartesian2();
var textureMatrixScratch = new Matrix3();
var quaternionScratch = new Quaternion();
var scratchNormal = new Cartesian3();
var scratchTangent = new Cartesian3();
var scratchBinormal = new Cartesian3();
var scratchCartographic = new Cartographic();
var projectedCenterScratch = new Cartesian3();
var scratchMinTexCoord = new Cartesian2();
var scratchMaxTexCoord = new Cartesian2();
function computeTopBottomAttributes(positions, options, extrude) {
var vertexFormat = options.vertexFormat;
var center = options.center;
var semiMajorAxis = options.semiMajorAxis;
var semiMinorAxis = options.semiMinorAxis;
var ellipsoid = options.ellipsoid;
var stRotation = options.stRotation;
var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3;
var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined;
var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(size * 3) : undefined;
var textureCoordIndex = 0;
// Raise positions to a height above the ellipsoid and compute the
// texture coordinates, normals, tangents, and binormals.
var normal = scratchNormal;
var tangent = scratchTangent;
var binormal = scratchBinormal;
var projection = new GeographicProjection(ellipsoid);
var projectedCenter = projection.project(ellipsoid.cartesianToCartographic(center, scratchCartographic), projectedCenterScratch);
var geodeticNormal = ellipsoid.scaleToGeodeticSurface(center, scratchCartesian1);
ellipsoid.geodeticSurfaceNormal(geodeticNormal, geodeticNormal);
var rotation = Quaternion.fromAxisAngle(geodeticNormal, stRotation, quaternionScratch);
var textureMatrix = Matrix3.fromQuaternion(rotation, textureMatrixScratch);
var minTexCoord = Cartesian2.fromElements(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, scratchMinTexCoord);
var maxTexCoord = Cartesian2.fromElements(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, scratchMaxTexCoord);
var length = positions.length;
var bottomOffset = (extrude) ? length : 0;
var stOffset = bottomOffset / 3 * 2;
for (var i = 0; i < length; i += 3) {
var i1 = i + 1;
var i2 = i + 2;
var position = Cartesian3.fromArray(positions, i, scratchCartesian1);
if (vertexFormat.st) {
var rotatedPoint = Matrix3.multiplyByVector(textureMatrix, position, scratchCartesian2);
var projectedPoint = projection.project(ellipsoid.cartesianToCartographic(rotatedPoint, scratchCartographic), scratchCartesian3);
Cartesian3.subtract(projectedPoint, projectedCenter, projectedPoint);
texCoordScratch.x = (projectedPoint.x + semiMajorAxis) / (2.0 * semiMajorAxis);
texCoordScratch.y = (projectedPoint.y + semiMinorAxis) / (2.0 * semiMinorAxis);
minTexCoord.x = Math.min(texCoordScratch.x, minTexCoord.x);
minTexCoord.y = Math.min(texCoordScratch.y, minTexCoord.y);
maxTexCoord.x = Math.max(texCoordScratch.x, maxTexCoord.x);
maxTexCoord.y = Math.max(texCoordScratch.y, maxTexCoord.y);
if (extrude) {
textureCoordinates[textureCoordIndex + stOffset] = texCoordScratch.x;
textureCoordinates[textureCoordIndex + 1 + stOffset] = texCoordScratch.y;
}
textureCoordinates[textureCoordIndex++] = texCoordScratch.x;
textureCoordinates[textureCoordIndex++] = texCoordScratch.y;
}
normal = ellipsoid.geodeticSurfaceNormal(position, normal);
if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
if (vertexFormat.tangent || vertexFormat.binormal) {
tangent = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent), tangent);
Matrix3.multiplyByVector(textureMatrix, tangent, tangent);
}
if (vertexFormat.normal) {
normals[i] = normal.x;
normals[i1] = normal.y;
normals[i2] = normal.z;
if (extrude) {
normals[i + bottomOffset] = -normal.x;
normals[i1 + bottomOffset] = -normal.y;
normals[i2 + bottomOffset] = -normal.z;
}
}
if (vertexFormat.tangent) {
tangents[i] = tangent.x;
tangents[i1] = tangent.y;
tangents[i2] = tangent.z;
if (extrude) {
tangents[i + bottomOffset] = -tangent.x;
tangents[i1 + bottomOffset] = -tangent.y;
tangents[i2 + bottomOffset] = -tangent.z;
}
}
if (vertexFormat.binormal) {
binormal = Cartesian3.normalize(Cartesian3.cross(normal, tangent, binormal), binormal);
binormals[i] = binormal.x;
binormals[i1] = binormal.y;
binormals[i2] = binormal.z;
if (extrude) {
binormals[i + bottomOffset] = binormal.x;
binormals[i1 + bottomOffset] = binormal.y;
binormals[i2 + bottomOffset] = binormal.z;
}
}
}
}
if (vertexFormat.st) {
length = textureCoordinates.length;
for (var k = 0; k < length; k += 2) {
textureCoordinates[k] = (textureCoordinates[k] - minTexCoord.x) / (maxTexCoord.x - minTexCoord.x);
textureCoordinates[k + 1] = (textureCoordinates[k + 1] - minTexCoord.y) / (maxTexCoord.y - minTexCoord.y);
}
}
var attributes = new GeometryAttributes();
if (vertexFormat.position) {
var finalPositions = EllipseGeometryLibrary.raisePositionsToHeight(positions, options, extrude);
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : finalPositions
});
}
if (vertexFormat.st) {
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : textureCoordinates
});
}
if (vertexFormat.normal) {
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.tangent) {
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
return attributes;
}
function topIndices(numPts) {
// numTriangles in half = 3 + 8 + 12 + ... = -1 + 4 + (4 + 4) + (4 + 4 + 4) + ... = -1 + 4 * (1 + 2 + 3 + ...)
// = -1 + 4 * ((n * ( n + 1)) / 2)
// total triangles = 2 * numTrangles in half
// indices = total triangles * 3;
// Substitute numPts for n above
var indices = new Array(12 * (numPts * ( numPts + 1)) - 6);
var indicesIndex = 0;
var prevIndex;
var numInterior;
var positionIndex;
var i;
var j;
// Indices triangles to the 'right' of the north vector
prevIndex = 0;
positionIndex = 1;
for (i = 0; i < 3; i++) {
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
}
for (i = 2; i < numPts + 1; ++i) {
positionIndex = i * (i + 1) - 1;
prevIndex = (i - 1) * i - 1;
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
numInterior = 2 * i;
for (j = 0; j < numInterior - 1; ++j) {
indices[indicesIndex++] = positionIndex;
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
}
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
}
// Indices for center column of triangles
numInterior = numPts * 2;
++positionIndex;
++prevIndex;
for (i = 0; i < numInterior - 1; ++i) {
indices[indicesIndex++] = positionIndex;
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
}
indices[indicesIndex++] = positionIndex;
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
// Reverse the process creating indices to the 'left' of the north vector
++prevIndex;
for (i = numPts - 1; i > 1; --i) {
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
numInterior = 2 * i;
for (j = 0; j < numInterior - 1; ++j) {
indices[indicesIndex++] = positionIndex;
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
}
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = positionIndex++;
}
for (i = 0; i < 3; i++) {
indices[indicesIndex++] = prevIndex++;
indices[indicesIndex++] = prevIndex;
indices[indicesIndex++] = positionIndex;
}
return indices;
}
var boundingSphereCenter = new Cartesian3();
function computeEllipse(options) {
var center = options.center;
boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter);
boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter);
var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis);
var cep = EllipseGeometryLibrary.computeEllipsePositions(options, true, false);
var positions = cep.positions;
var numPts = cep.numPts;
var attributes = computeTopBottomAttributes(positions, options, false);
var indices = topIndices(numPts);
indices = IndexDatatype.createTypedArray(positions.length / 3, indices);
return {
boundingSphere : boundingSphere,
attributes : attributes,
indices : indices
};
}
function computeWallAttributes(positions, options) {
var vertexFormat = options.vertexFormat;
var center = options.center;
var semiMajorAxis = options.semiMajorAxis;
var semiMinorAxis = options.semiMinorAxis;
var ellipsoid = options.ellipsoid;
var height = options.height;
var extrudedHeight = options.extrudedHeight;
var stRotation = options.stRotation;
var size = positions.length / 3 * 2;
var finalPositions = new Float64Array(size * 3);
var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined;
var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(size * 3) : undefined;
var textureCoordIndex = 0;
// Raise positions to a height above the ellipsoid and compute the
// texture coordinates, normals, tangents, and binormals.
var normal = scratchNormal;
var tangent = scratchTangent;
var binormal = scratchBinormal;
var projection = new GeographicProjection(ellipsoid);
var projectedCenter = projection.project(ellipsoid.cartesianToCartographic(center, scratchCartographic), projectedCenterScratch);
var geodeticNormal = ellipsoid.scaleToGeodeticSurface(center, scratchCartesian1);
ellipsoid.geodeticSurfaceNormal(geodeticNormal, geodeticNormal);
var rotation = Quaternion.fromAxisAngle(geodeticNormal, stRotation, quaternionScratch);
var textureMatrix = Matrix3.fromQuaternion(rotation, textureMatrixScratch);
var minTexCoord = Cartesian2.fromElements(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, scratchMinTexCoord);
var maxTexCoord = Cartesian2.fromElements(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, scratchMaxTexCoord);
var length = positions.length;
var stOffset = length / 3 * 2;
for (var i = 0; i < length; i += 3) {
var i1 = i + 1;
var i2 = i + 2;
var position = Cartesian3.fromArray(positions, i, scratchCartesian1);
var extrudedPosition;
if (vertexFormat.st) {
var rotatedPoint = Matrix3.multiplyByVector(textureMatrix, position, scratchCartesian2);
var projectedPoint = projection.project(ellipsoid.cartesianToCartographic(rotatedPoint, scratchCartographic), scratchCartesian3);
Cartesian3.subtract(projectedPoint, projectedCenter, projectedPoint);
texCoordScratch.x = (projectedPoint.x + semiMajorAxis) / (2.0 * semiMajorAxis);
texCoordScratch.y = (projectedPoint.y + semiMinorAxis) / (2.0 * semiMinorAxis);
minTexCoord.x = Math.min(texCoordScratch.x, minTexCoord.x);
minTexCoord.y = Math.min(texCoordScratch.y, minTexCoord.y);
maxTexCoord.x = Math.max(texCoordScratch.x, maxTexCoord.x);
maxTexCoord.y = Math.max(texCoordScratch.y, maxTexCoord.y);
textureCoordinates[textureCoordIndex + stOffset] = texCoordScratch.x;
textureCoordinates[textureCoordIndex + 1 + stOffset] = texCoordScratch.y;
textureCoordinates[textureCoordIndex++] = texCoordScratch.x;
textureCoordinates[textureCoordIndex++] = texCoordScratch.y;
}
position = ellipsoid.scaleToGeodeticSurface(position, position);
extrudedPosition = Cartesian3.clone(position, scratchCartesian2);
normal = ellipsoid.geodeticSurfaceNormal(position, normal);
var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian4);
position = Cartesian3.add(position, scaledNormal, position);
scaledNormal = Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal);
extrudedPosition = Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition);
if (vertexFormat.position) {
finalPositions[i + length] = extrudedPosition.x;
finalPositions[i1 + length] = extrudedPosition.y;
finalPositions[i2 + length] = extrudedPosition.z;
finalPositions[i] = position.x;
finalPositions[i1] = position.y;
finalPositions[i2] = position.z;
}
if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
binormal = Cartesian3.clone(normal, binormal);
var next = Cartesian3.fromArray(positions, (i + 3) % length, scratchCartesian4);
Cartesian3.subtract(next, position, next);
var bottom = Cartesian3.subtract(extrudedPosition, position, scratchCartesian3);
normal = Cartesian3.normalize(Cartesian3.cross(bottom, next, normal), normal);
if (vertexFormat.normal) {
normals[i] = normal.x;
normals[i1] = normal.y;
normals[i2] = normal.z;
normals[i + length] = normal.x;
normals[i1 + length] = normal.y;
normals[i2 + length] = normal.z;
}
if (vertexFormat.tangent) {
tangent = Cartesian3.normalize(Cartesian3.cross(binormal, normal, tangent), tangent);
tangents[i] = tangent.x;
tangents[i1] = tangent.y;
tangents[i2] = tangent.z;
tangents[i + length] = tangent.x;
tangents[i + 1 + length] = tangent.y;
tangents[i + 2 + length] = tangent.z;
}
if (vertexFormat.binormal) {
binormals[i] = binormal.x;
binormals[i1] = binormal.y;
binormals[i2] = binormal.z;
binormals[i + length] = binormal.x;
binormals[i1 + length] = binormal.y;
binormals[i2 + length] = binormal.z;
}
}
}
if (vertexFormat.st) {
length = textureCoordinates.length;
for (var k = 0; k < length; k += 2) {
textureCoordinates[k] = (textureCoordinates[k] - minTexCoord.x) / (maxTexCoord.x - minTexCoord.x);
textureCoordinates[k + 1] = (textureCoordinates[k + 1] - minTexCoord.y) / (maxTexCoord.y - minTexCoord.y);
}
}
var attributes = new GeometryAttributes();
if (vertexFormat.position) {
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : finalPositions
});
}
if (vertexFormat.st) {
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : textureCoordinates
});
}
if (vertexFormat.normal) {
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.tangent) {
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
return attributes;
}
function computeWallIndices(positions) {
var length = positions.length / 3;
var indices = IndexDatatype.createTypedArray(length, length * 6);
var index = 0;
for (var i = 0; i < length; i++) {
var UL = i;
var LL = i + length;
var UR = (UL + 1) % length;
var LR = UR + length;
indices[index++] = UL;
indices[index++] = LL;
indices[index++] = UR;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
}
return indices;
}
var topBoundingSphere = new BoundingSphere();
var bottomBoundingSphere = new BoundingSphere();
function computeExtrudedEllipse(options) {
var center = options.center;
var ellipsoid = options.ellipsoid;
var semiMajorAxis = options.semiMajorAxis;
var scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scratchCartesian1), options.height, scratchCartesian1);
topBoundingSphere.center = Cartesian3.add(center, scaledNormal, topBoundingSphere.center);
topBoundingSphere.radius = semiMajorAxis;
scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scaledNormal), options.extrudedHeight, scaledNormal);
bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center);
bottomBoundingSphere.radius = semiMajorAxis;
var cep = EllipseGeometryLibrary.computeEllipsePositions(options, true, true);
var positions = cep.positions;
var numPts = cep.numPts;
var outerPositions = cep.outerPositions;
var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere);
var topBottomAttributes = computeTopBottomAttributes(positions, options, true);
var indices = topIndices(numPts);
var length = indices.length;
indices.length = length * 2;
var posLength = positions.length / 3;
for (var i = 0; i < length; i += 3) {
indices[i + length] = indices[i + 2] + posLength;
indices[i + 1 + length] = indices[i + 1] + posLength;
indices[i + 2 + length] = indices[i] + posLength;
}
var topBottomIndices = IndexDatatype.createTypedArray(posLength * 2 / 3, indices);
var topBottomGeo = new Geometry({
attributes : topBottomAttributes,
indices : topBottomIndices,
primitiveType : PrimitiveType.TRIANGLES
});
var wallAttributes = computeWallAttributes(outerPositions, options);
indices = computeWallIndices(outerPositions);
var wallIndices = IndexDatatype.createTypedArray(outerPositions.length * 2 / 3, indices);
var wallGeo = new Geometry({
attributes : wallAttributes,
indices : wallIndices,
primitiveType : PrimitiveType.TRIANGLES
});
var geo = GeometryPipeline.combineInstances([
new GeometryInstance({
geometry : topBottomGeo
}),
new GeometryInstance({
geometry : wallGeo
})
]);
return {
boundingSphere : boundingSphere,
attributes : geo[0].attributes,
indices : geo[0].indices
};
}
var scratchEnuToFixedMatrix = new Matrix4();
var scratchFixedToEnuMatrix = new Matrix4();
var scratchRotationMatrix = new Matrix3();
var scratchRectanglePoints = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
var scratchCartographicPoints = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
function computeRectangle(center, ellipsoid, semiMajorAxis, semiMinorAxis, rotation) {
Transforms.eastNorthUpToFixedFrame(center, ellipsoid, scratchEnuToFixedMatrix);
Matrix4.inverseTransformation(scratchEnuToFixedMatrix, scratchFixedToEnuMatrix);
// Find the 4 extreme points of the ellipse in ENU
for (var i = 0; i < 4; ++i) {
Cartesian3.clone(Cartesian3.ZERO, scratchRectanglePoints[i]);
}
scratchRectanglePoints[0].x += semiMajorAxis;
scratchRectanglePoints[1].x -= semiMajorAxis;
scratchRectanglePoints[2].y += semiMinorAxis;
scratchRectanglePoints[3].y -= semiMinorAxis;
Matrix3.fromRotationZ(rotation, scratchRotationMatrix);
for (i = 0; i < 4; ++i) {
// Apply the rotation
Matrix3.multiplyByVector(scratchRotationMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]);
// Convert back to fixed and then to cartographic
Matrix4.multiplyByPoint(scratchEnuToFixedMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]);
ellipsoid.cartesianToCartographic(scratchRectanglePoints[i], scratchCartographicPoints[i]);
}
return Rectangle.fromCartographicArray(scratchCartographicPoints);
}
/**
* A description of an ellipse on an ellipsoid. Ellipse geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
*
* @alias EllipseGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.center The ellipse's center point in the fixed frame.
* @param {Number} options.semiMajorAxis The length of the ellipse's semi-major axis in meters.
* @param {Number} options.semiMinorAxis The length of the ellipse's semi-minor axis in meters.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the ellipse will be on.
* @param {Number} [options.height=0.0] The distance in meters between the ellipse and the ellipsoid surface.
* @param {Number} [options.extrudedHeight] The distance in meters between the ellipse's extruded face and the ellipsoid surface.
* @param {Number} [options.rotation=0.0] The angle of rotation counter-clockwise from north.
* @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates counter-clockwise from north.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The angular distance between points on the ellipse in radians.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
*
* @exception {DeveloperError} semiMajorAxis and semiMinorAxis must be greater than zero.
* @exception {DeveloperError} semiMajorAxis must be greater than or equal to the semiMinorAxis.
* @exception {DeveloperError} granularity must be greater than zero.
*
*
* @example
* // Create an ellipse.
* var ellipse = new Cesium.EllipseGeometry({
* center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
* semiMajorAxis : 500000.0,
* semiMinorAxis : 300000.0,
* rotation : Cesium.Math.toRadians(60.0)
* });
* var geometry = Cesium.EllipseGeometry.createGeometry(ellipse);
*
* @see EllipseGeometry.createGeometry
*/
function EllipseGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var center = options.center;
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var semiMajorAxis = options.semiMajorAxis;
var semiMinorAxis = options.semiMinorAxis;
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var height = defaultValue(options.height, 0.0);
var extrudedHeight = options.extrudedHeight;
var extrude = (defined(extrudedHeight) && Math.abs(height - extrudedHeight) > 1.0);
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
if (!defined(center)) {
throw new DeveloperError('center is required.');
}
if (!defined(semiMajorAxis)) {
throw new DeveloperError('semiMajorAxis is required.');
}
if (!defined(semiMinorAxis)) {
throw new DeveloperError('semiMinorAxis is required.');
}
if (semiMajorAxis < semiMinorAxis) {
throw new DeveloperError('semiMajorAxis must be greater than or equal to the semiMinorAxis.');
}
if (granularity <= 0.0) {
throw new DeveloperError('granularity must be greater than zero.');
}
this._center = Cartesian3.clone(center);
this._semiMajorAxis = semiMajorAxis;
this._semiMinorAxis = semiMinorAxis;
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._rotation = defaultValue(options.rotation, 0.0);
this._stRotation = defaultValue(options.stRotation, 0.0);
this._height = height;
this._granularity = granularity;
this._vertexFormat = VertexFormat.clone(vertexFormat);
this._extrudedHeight = defaultValue(extrudedHeight, height);
this._extrude = extrude;
this._workerName = 'createEllipseGeometry';
this._rectangle = computeRectangle(this._center, this._ellipsoid, semiMajorAxis, semiMinorAxis, this._rotation);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
EllipseGeometry.packedLength = Cartesian3.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 8;
/**
* Stores the provided instance into the provided array.
*
* @param {EllipseGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
EllipseGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._center, array, startingIndex);
startingIndex += Cartesian3.packedLength;
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
Rectangle.pack(value._rectangle, array, startingIndex);
startingIndex += Rectangle.packedLength;
array[startingIndex++] = value._semiMajorAxis;
array[startingIndex++] = value._semiMinorAxis;
array[startingIndex++] = value._rotation;
array[startingIndex++] = value._stRotation;
array[startingIndex++] = value._height;
array[startingIndex++] = value._granularity;
array[startingIndex++] = value._extrudedHeight;
array[startingIndex] = value._extrude ? 1.0 : 0.0;
return array;
};
var scratchCenter = new Cartesian3();
var scratchEllipsoid = new Ellipsoid();
var scratchVertexFormat = new VertexFormat();
var scratchRectangle = new Rectangle();
var scratchOptions = {
center : scratchCenter,
ellipsoid : scratchEllipsoid,
vertexFormat : scratchVertexFormat,
semiMajorAxis : undefined,
semiMinorAxis : undefined,
rotation : undefined,
stRotation : undefined,
height : undefined,
granularity : undefined,
extrudedHeight : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {EllipseGeometry} [result] The object into which to store the result.
* @returns {EllipseGeometry} The modified result parameter or a new EllipseGeometry instance if one was not provided.
*/
EllipseGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var center = Cartesian3.unpack(array, startingIndex, scratchCenter);
startingIndex += Cartesian3.packedLength;
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
startingIndex += Rectangle.packedLength;
var semiMajorAxis = array[startingIndex++];
var semiMinorAxis = array[startingIndex++];
var rotation = array[startingIndex++];
var stRotation = array[startingIndex++];
var height = array[startingIndex++];
var granularity = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var extrude = array[startingIndex] === 1.0;
if (!defined(result)) {
scratchOptions.height = height;
scratchOptions.extrudedHeight = extrudedHeight;
scratchOptions.granularity = granularity;
scratchOptions.stRotation = stRotation;
scratchOptions.rotation = rotation;
scratchOptions.semiMajorAxis = semiMajorAxis;
scratchOptions.semiMinorAxis = semiMinorAxis;
return new EllipseGeometry(scratchOptions);
}
result._center = Cartesian3.clone(center, result._center);
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._semiMajorAxis = semiMajorAxis;
result._semiMinorAxis = semiMinorAxis;
result._rotation = rotation;
result._stRotation = stRotation;
result._height = height;
result._granularity = granularity;
result._extrudedHeight = extrudedHeight;
result._extrude = extrude;
result._rectangle = Rectangle.clone(rectangle);
return result;
};
/**
* Computes the geometric representation of a ellipse on an ellipsoid, including its vertices, indices, and a bounding sphere.
*
* @param {EllipseGeometry} ellipseGeometry A description of the ellipse.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
EllipseGeometry.createGeometry = function(ellipseGeometry) {
if ((ellipseGeometry._semiMajorAxis <= 0.0) || (ellipseGeometry._semiMinorAxis <= 0.0)) {
return;
}
ellipseGeometry._center = ellipseGeometry._ellipsoid.scaleToGeodeticSurface(ellipseGeometry._center, ellipseGeometry._center);
var options = {
center : ellipseGeometry._center,
semiMajorAxis : ellipseGeometry._semiMajorAxis,
semiMinorAxis : ellipseGeometry._semiMinorAxis,
ellipsoid : ellipseGeometry._ellipsoid,
rotation : ellipseGeometry._rotation,
height : ellipseGeometry._height,
extrudedHeight : ellipseGeometry._extrudedHeight,
granularity : ellipseGeometry._granularity,
vertexFormat : ellipseGeometry._vertexFormat,
stRotation : ellipseGeometry._stRotation
};
var geometry;
if (ellipseGeometry._extrude) {
options.extrudedHeight = Math.min(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
options.height = Math.max(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
geometry = computeExtrudedEllipse(options);
} else {
geometry = computeEllipse(options);
}
return new Geometry({
attributes : geometry.attributes,
indices : geometry.indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : geometry.boundingSphere
});
};
/**
* @private
*/
EllipseGeometry.createShadowVolume = function(ellipseGeometry, minHeightFunc, maxHeightFunc) {
var granularity = ellipseGeometry._granularity;
var ellipsoid = ellipseGeometry._ellipsoid;
var minHeight = minHeightFunc(granularity, ellipsoid);
var maxHeight = maxHeightFunc(granularity, ellipsoid);
return new EllipseGeometry({
center : ellipseGeometry._center,
semiMajorAxis : ellipseGeometry._semiMajorAxis,
semiMinorAxis : ellipseGeometry._semiMinorAxis,
ellipsoid : ellipsoid,
rotation : ellipseGeometry._rotation,
stRotation : ellipseGeometry._stRotation,
granularity : granularity,
extrudedHeight : minHeight,
height : maxHeight,
vertexFormat : VertexFormat.POSITION_ONLY
});
};
defineProperties(EllipseGeometry.prototype, {
/**
* @private
*/
rectangle : {
get : function() {
return this._rectangle;
}
}
});
return EllipseGeometry;
});
/*global define*/
define('Core/CircleGeometry',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./EllipseGeometry',
'./Ellipsoid',
'./VertexFormat'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
EllipseGeometry,
Ellipsoid,
VertexFormat) {
'use strict';
/**
* A description of a circle on the ellipsoid. Circle geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
*
* @alias CircleGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.center The circle's center point in the fixed frame.
* @param {Number} options.radius The radius in meters.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the circle will be on.
* @param {Number} [options.height=0.0] The distance in meters between the circle and the ellipsoid surface.
* @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {Number} [options.extrudedHeight=0.0] The distance in meters between the circle's extruded face and the ellipsoid surface.
* @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
*
* @exception {DeveloperError} radius must be greater than zero.
* @exception {DeveloperError} granularity must be greater than zero.
*
* @see CircleGeometry.createGeometry
* @see Packable
*
* @example
* // Create a circle.
* var circle = new Cesium.CircleGeometry({
* center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
* radius : 100000.0
* });
* var geometry = Cesium.CircleGeometry.createGeometry(circle);
*/
function CircleGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var radius = options.radius;
if (!defined(radius)) {
throw new DeveloperError('radius is required.');
}
var ellipseGeometryOptions = {
center : options.center,
semiMajorAxis : radius,
semiMinorAxis : radius,
ellipsoid : options.ellipsoid,
height : options.height,
extrudedHeight : options.extrudedHeight,
granularity : options.granularity,
vertexFormat : options.vertexFormat,
stRotation : options.stRotation
};
this._ellipseGeometry = new EllipseGeometry(ellipseGeometryOptions);
this._workerName = 'createCircleGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
CircleGeometry.packedLength = EllipseGeometry.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {CircleGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
CircleGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
return EllipseGeometry.pack(value._ellipseGeometry, array, startingIndex);
};
var scratchEllipseGeometry = new EllipseGeometry({
center : new Cartesian3(),
semiMajorAxis : 1.0,
semiMinorAxis : 1.0
});
var scratchOptions = {
center : new Cartesian3(),
radius : undefined,
ellipsoid : Ellipsoid.clone(Ellipsoid.UNIT_SPHERE),
height : undefined,
extrudedHeight : undefined,
granularity : undefined,
vertexFormat : new VertexFormat(),
stRotation : undefined,
semiMajorAxis : undefined,
semiMinorAxis : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {CircleGeometry} [result] The object into which to store the result.
* @returns {CircleGeometry} The modified result parameter or a new CircleGeometry instance if one was not provided.
*/
CircleGeometry.unpack = function(array, startingIndex, result) {
var ellipseGeometry = EllipseGeometry.unpack(array, startingIndex, scratchEllipseGeometry);
scratchOptions.center = Cartesian3.clone(ellipseGeometry._center, scratchOptions.center);
scratchOptions.ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid, scratchOptions.ellipsoid);
scratchOptions.height = ellipseGeometry._height;
scratchOptions.extrudedHeight = ellipseGeometry._extrudedHeight;
scratchOptions.granularity = ellipseGeometry._granularity;
scratchOptions.vertexFormat = VertexFormat.clone(ellipseGeometry._vertexFormat, scratchOptions.vertexFormat);
scratchOptions.stRotation = ellipseGeometry._stRotation;
if (!defined(result)) {
scratchOptions.radius = ellipseGeometry._semiMajorAxis;
return new CircleGeometry(scratchOptions);
}
scratchOptions.semiMajorAxis = ellipseGeometry._semiMajorAxis;
scratchOptions.semiMinorAxis = ellipseGeometry._semiMinorAxis;
result._ellipseGeometry = new EllipseGeometry(scratchOptions);
return result;
};
/**
* Computes the geometric representation of a circle on an ellipsoid, including its vertices, indices, and a bounding sphere.
*
* @param {CircleGeometry} circleGeometry A description of the circle.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
CircleGeometry.createGeometry = function(circleGeometry) {
return EllipseGeometry.createGeometry(circleGeometry._ellipseGeometry);
};
/**
* @private
*/
CircleGeometry.createShadowVolume = function(circleGeometry, minHeightFunc, maxHeightFunc) {
var granularity = circleGeometry._ellipseGeometry._granularity;
var ellipsoid = circleGeometry._ellipseGeometry._ellipsoid;
var minHeight = minHeightFunc(granularity, ellipsoid);
var maxHeight = maxHeightFunc(granularity, ellipsoid);
return new CircleGeometry({
center : circleGeometry._ellipseGeometry._center,
radius : circleGeometry._ellipseGeometry._semiMajorAxis,
ellipsoid : ellipsoid,
stRotation : circleGeometry._ellipseGeometry._stRotation,
granularity : granularity,
extrudedHeight : minHeight,
height : maxHeight,
vertexFormat : VertexFormat.POSITION_ONLY
});
};
defineProperties(CircleGeometry.prototype, {
/**
* @private
*/
rectangle : {
get : function() {
return this._ellipseGeometry.rectangle;
}
}
});
return CircleGeometry;
});
/*global define*/
define('Core/EllipseOutlineGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./EllipseGeometryLibrary',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PrimitiveType'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
EllipseGeometryLibrary,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PrimitiveType) {
'use strict';
var scratchCartesian1 = new Cartesian3();
var boundingSphereCenter = new Cartesian3();
function computeEllipse(options) {
var center = options.center;
boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter);
boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter);
var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis);
var positions = EllipseGeometryLibrary.computeEllipsePositions(options, false, true).outerPositions;
var attributes = new GeometryAttributes({
position: new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : EllipseGeometryLibrary.raisePositionsToHeight(positions, options, false)
})
});
var length = positions.length / 3;
var indices = IndexDatatype.createTypedArray(length, length * 2);
var index = 0;
for ( var i = 0; i < length; ++i) {
indices[index++] = i;
indices[index++] = (i + 1) % length;
}
return {
boundingSphere : boundingSphere,
attributes : attributes,
indices : indices
};
}
var topBoundingSphere = new BoundingSphere();
var bottomBoundingSphere = new BoundingSphere();
function computeExtrudedEllipse(options) {
var center = options.center;
var ellipsoid = options.ellipsoid;
var semiMajorAxis = options.semiMajorAxis;
var scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scratchCartesian1), options.height, scratchCartesian1);
topBoundingSphere.center = Cartesian3.add(center, scaledNormal, topBoundingSphere.center);
topBoundingSphere.radius = semiMajorAxis;
scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scaledNormal), options.extrudedHeight, scaledNormal);
bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center);
bottomBoundingSphere.radius = semiMajorAxis;
var positions = EllipseGeometryLibrary.computeEllipsePositions(options, false, true).outerPositions;
var attributes = new GeometryAttributes({
position: new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : EllipseGeometryLibrary.raisePositionsToHeight(positions, options, true)
})
});
positions = attributes.position.values;
var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere);
var length = positions.length/3;
var numberOfVerticalLines = defaultValue(options.numberOfVerticalLines, 16);
numberOfVerticalLines = CesiumMath.clamp(numberOfVerticalLines, 0, length/2);
var indices = IndexDatatype.createTypedArray(length, length * 2 + numberOfVerticalLines * 2);
length /= 2;
var index = 0;
var i;
for (i = 0; i < length; ++i) {
indices[index++] = i;
indices[index++] = (i + 1) % length;
indices[index++] = i + length;
indices[index++] = ((i + 1) % length) + length;
}
var numSide;
if (numberOfVerticalLines > 0) {
var numSideLines = Math.min(numberOfVerticalLines, length);
numSide = Math.round(length / numSideLines);
var maxI = Math.min(numSide * numberOfVerticalLines, length);
for (i = 0; i < maxI; i += numSide) {
indices[index++] = i;
indices[index++] = i + length;
}
}
return {
boundingSphere : boundingSphere,
attributes : attributes,
indices : indices
};
}
/**
* A description of the outline of an ellipse on an ellipsoid.
*
* @alias EllipseOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.center The ellipse's center point in the fixed frame.
* @param {Number} options.semiMajorAxis The length of the ellipse's semi-major axis in meters.
* @param {Number} options.semiMinorAxis The length of the ellipse's semi-minor axis in meters.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the ellipse will be on.
* @param {Number} [options.height=0.0] The distance in meters between the ellipse and the ellipsoid surface.
* @param {Number} [options.extrudedHeight] The distance in meters between the ellipse's extruded face and the ellipsoid surface.
* @param {Number} [options.rotation=0.0] The angle from north (counter-clockwise) in radians.
* @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians.
* @param {Number} [options.numberOfVerticalLines=16] Number of lines to draw between the top and bottom surface of an extruded ellipse.
*
* @exception {DeveloperError} semiMajorAxis and semiMinorAxis must be greater than zero.
* @exception {DeveloperError} semiMajorAxis must be greater than or equal to the semiMinorAxis.
* @exception {DeveloperError} granularity must be greater than zero.
*
* @see EllipseOutlineGeometry.createGeometry
*
* @example
* var ellipse = new Cesium.EllipseOutlineGeometry({
* center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
* semiMajorAxis : 500000.0,
* semiMinorAxis : 300000.0,
* rotation : Cesium.Math.toRadians(60.0)
* });
* var geometry = Cesium.EllipseOutlineGeometry.createGeometry(ellipse);
*/
function EllipseOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var center = options.center;
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var semiMajorAxis = options.semiMajorAxis;
var semiMinorAxis = options.semiMinorAxis;
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var height = defaultValue(options.height, 0.0);
var extrudedHeight = options.extrudedHeight;
var extrude = (defined(extrudedHeight) && Math.abs(height - extrudedHeight) > 1.0);
if (!defined(center)) {
throw new DeveloperError('center is required.');
}
if (!defined(semiMajorAxis)) {
throw new DeveloperError('semiMajorAxis is required.');
}
if (!defined(semiMinorAxis)) {
throw new DeveloperError('semiMinorAxis is required.');
}
if (semiMajorAxis < semiMinorAxis) {
throw new DeveloperError('semiMajorAxis must be greater than or equal to the semiMinorAxis.');
}
if (granularity <= 0.0) {
throw new DeveloperError('granularity must be greater than zero.');
}
this._center = Cartesian3.clone(center);
this._semiMajorAxis = semiMajorAxis;
this._semiMinorAxis = semiMinorAxis;
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._rotation = defaultValue(options.rotation, 0.0);
this._height = height;
this._granularity = granularity;
this._extrudedHeight = extrudedHeight;
this._extrude = extrude;
this._numberOfVerticalLines = Math.max(defaultValue(options.numberOfVerticalLines, 16), 0);
this._workerName = 'createEllipseOutlineGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
EllipseOutlineGeometry.packedLength = Cartesian3.packedLength + Ellipsoid.packedLength + 9;
/**
* Stores the provided instance into the provided array.
*
* @param {EllipseOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
EllipseOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._center, array, startingIndex);
startingIndex += Cartesian3.packedLength;
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex++] = value._semiMajorAxis;
array[startingIndex++] = value._semiMinorAxis;
array[startingIndex++] = value._rotation;
array[startingIndex++] = value._height;
array[startingIndex++] = value._granularity;
array[startingIndex++] = defined(value._extrudedHeight) ? 1.0 : 0.0;
array[startingIndex++] = defaultValue(value._extrudedHeight, 0.0);
array[startingIndex++] = value._extrude ? 1.0 : 0.0;
array[startingIndex] = value._numberOfVerticalLines;
return array;
};
var scratchCenter = new Cartesian3();
var scratchEllipsoid = new Ellipsoid();
var scratchOptions = {
center : scratchCenter,
ellipsoid : scratchEllipsoid,
semiMajorAxis : undefined,
semiMinorAxis : undefined,
rotation : undefined,
height : undefined,
granularity : undefined,
extrudedHeight : undefined,
numberOfVerticalLines : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {EllipseOutlineGeometry} [result] The object into which to store the result.
* @returns {EllipseOutlineGeometry} The modified result parameter or a new EllipseOutlineGeometry instance if one was not provided.
*/
EllipseOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var center = Cartesian3.unpack(array, startingIndex, scratchCenter);
startingIndex += Cartesian3.packedLength;
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var semiMajorAxis = array[startingIndex++];
var semiMinorAxis = array[startingIndex++];
var rotation = array[startingIndex++];
var height = array[startingIndex++];
var granularity = array[startingIndex++];
var hasExtrudedHeight = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var extrude = array[startingIndex++] === 1.0;
var numberOfVerticalLines = array[startingIndex];
if (!defined(result)) {
scratchOptions.height = height;
scratchOptions.extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
scratchOptions.granularity = granularity;
scratchOptions.rotation = rotation;
scratchOptions.semiMajorAxis = semiMajorAxis;
scratchOptions.semiMinorAxis = semiMinorAxis;
scratchOptions.numberOfVerticalLines = numberOfVerticalLines;
return new EllipseOutlineGeometry(scratchOptions);
}
result._center = Cartesian3.clone(center, result._center);
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._semiMajorAxis = semiMajorAxis;
result._semiMinorAxis = semiMinorAxis;
result._rotation = rotation;
result._height = height;
result._granularity = granularity;
result._extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
result._extrude = extrude;
result._numberOfVerticalLines = numberOfVerticalLines;
return result;
};
/**
* Computes the geometric representation of an outline of an ellipse on an ellipsoid, including its vertices, indices, and a bounding sphere.
*
* @param {EllipseOutlineGeometry} ellipseGeometry A description of the ellipse.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
EllipseOutlineGeometry.createGeometry = function(ellipseGeometry) {
if ((ellipseGeometry._semiMajorAxis <= 0.0) || (ellipseGeometry._semiMinorAxis <= 0.0)) {
return;
}
ellipseGeometry._center = ellipseGeometry._ellipsoid.scaleToGeodeticSurface(ellipseGeometry._center, ellipseGeometry._center);
var options = {
center : ellipseGeometry._center,
semiMajorAxis : ellipseGeometry._semiMajorAxis,
semiMinorAxis : ellipseGeometry._semiMinorAxis,
ellipsoid : ellipseGeometry._ellipsoid,
rotation : ellipseGeometry._rotation,
height : ellipseGeometry._height,
extrudedHeight : ellipseGeometry._extrudedHeight,
granularity : ellipseGeometry._granularity,
numberOfVerticalLines : ellipseGeometry._numberOfVerticalLines
};
var geometry;
if (ellipseGeometry._extrude) {
options.extrudedHeight = Math.min(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
options.height = Math.max(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
geometry = computeExtrudedEllipse(options);
} else {
geometry = computeEllipse(options);
}
return new Geometry({
attributes : geometry.attributes,
indices : geometry.indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : geometry.boundingSphere
});
};
return EllipseOutlineGeometry;
});
/*global define*/
define('Core/CircleOutlineGeometry',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./EllipseOutlineGeometry',
'./Ellipsoid'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
EllipseOutlineGeometry,
Ellipsoid) {
'use strict';
/**
* A description of the outline of a circle on the ellipsoid.
*
* @alias CircleOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3} options.center The circle's center point in the fixed frame.
* @param {Number} options.radius The radius in meters.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the circle will be on.
* @param {Number} [options.height=0.0] The distance in meters between the circle and the ellipsoid surface.
* @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians.
* @param {Number} [options.extrudedHeight=0.0] The distance in meters between the circle's extruded face and the ellipsoid surface.
* @param {Number} [options.numberOfVerticalLines=16] Number of lines to draw between the top and bottom of an extruded circle.
*
* @exception {DeveloperError} radius must be greater than zero.
* @exception {DeveloperError} granularity must be greater than zero.
*
* @see CircleOutlineGeometry.createGeometry
* @see Packable
*
* @example
* // Create a circle.
* var circle = new Cesium.CircleOutlineGeometry({
* center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
* radius : 100000.0
* });
* var geometry = Cesium.CircleOutlineGeometry.createGeometry(circle);
*/
function CircleOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var radius = options.radius;
if (!defined(radius)) {
throw new DeveloperError('radius is required.');
}
var ellipseGeometryOptions = {
center : options.center,
semiMajorAxis : radius,
semiMinorAxis : radius,
ellipsoid : options.ellipsoid,
height : options.height,
extrudedHeight : options.extrudedHeight,
granularity : options.granularity,
numberOfVerticalLines : options.numberOfVerticalLines
};
this._ellipseGeometry = new EllipseOutlineGeometry(ellipseGeometryOptions);
this._workerName = 'createCircleOutlineGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
CircleOutlineGeometry.packedLength = EllipseOutlineGeometry.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {CircleOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
CircleOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
return EllipseOutlineGeometry.pack(value._ellipseGeometry, array, startingIndex);
};
var scratchEllipseGeometry = new EllipseOutlineGeometry({
center : new Cartesian3(),
semiMajorAxis : 1.0,
semiMinorAxis : 1.0
});
var scratchOptions = {
center : new Cartesian3(),
radius : undefined,
ellipsoid : Ellipsoid.clone(Ellipsoid.UNIT_SPHERE),
height : undefined,
extrudedHeight : undefined,
granularity : undefined,
numberOfVerticalLines : undefined,
semiMajorAxis : undefined,
semiMinorAxis : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {CircleOutlineGeometry} [result] The object into which to store the result.
* @returns {CircleOutlineGeometry} The modified result parameter or a new CircleOutlineGeometry instance if one was not provided.
*/
CircleOutlineGeometry.unpack = function(array, startingIndex, result) {
var ellipseGeometry = EllipseOutlineGeometry.unpack(array, startingIndex, scratchEllipseGeometry);
scratchOptions.center = Cartesian3.clone(ellipseGeometry._center, scratchOptions.center);
scratchOptions.ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid, scratchOptions.ellipsoid);
scratchOptions.height = ellipseGeometry._height;
scratchOptions.extrudedHeight = ellipseGeometry._extrudedHeight;
scratchOptions.granularity = ellipseGeometry._granularity;
scratchOptions.numberOfVerticalLines = ellipseGeometry._numberOfVerticalLines;
if (!defined(result)) {
scratchOptions.radius = ellipseGeometry._semiMajorAxis;
return new CircleOutlineGeometry(scratchOptions);
}
scratchOptions.semiMajorAxis = ellipseGeometry._semiMajorAxis;
scratchOptions.semiMinorAxis = ellipseGeometry._semiMinorAxis;
result._ellipseGeometry = new EllipseOutlineGeometry(scratchOptions);
return result;
};
/**
* Computes the geometric representation of an outline of a circle on an ellipsoid, including its vertices, indices, and a bounding sphere.
*
* @param {CircleOutlineGeometry} circleGeometry A description of the circle.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
CircleOutlineGeometry.createGeometry = function(circleGeometry) {
return EllipseOutlineGeometry.createGeometry(circleGeometry._ellipseGeometry);
};
return CircleOutlineGeometry;
});
/*global define*/
define('Core/ClockRange',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Constants used by {@link Clock#tick} to determine behavior
* when {@link Clock#startTime} or {@link Clock#stopTime} is reached.
*
* @exports ClockRange
*
* @see Clock
* @see ClockStep
*/
var ClockRange = {
/**
* {@link Clock#tick} will always advances the clock in its current direction.
*
* @type {Number}
* @constant
*/
UNBOUNDED : 0,
/**
* When {@link Clock#startTime} or {@link Clock#stopTime} is reached,
* {@link Clock#tick} will not advance {@link Clock#currentTime} any further.
*
* @type {Number}
* @constant
*/
CLAMPED : 1,
/**
* When {@link Clock#stopTime} is reached, {@link Clock#tick} will advance
* {@link Clock#currentTime} to the opposite end of the interval. When
* time is moving backwards, {@link Clock#tick} will not advance past
* {@link Clock#startTime}
*
* @type {Number}
* @constant
*/
LOOP_STOP : 2
};
return freezeObject(ClockRange);
});
/*global define*/
define('Core/ClockStep',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Constants to determine how much time advances with each call
* to {@link Clock#tick}.
*
* @exports ClockStep
*
* @see Clock
* @see ClockRange
*/
var ClockStep = {
/**
* {@link Clock#tick} advances the current time by a fixed step,
* which is the number of seconds specified by {@link Clock#multiplier}.
*
* @type {Number}
* @constant
*/
TICK_DEPENDENT : 0,
/**
* {@link Clock#tick} advances the current time by the amount of system
* time elapsed since the previous call multiplied by {@link Clock#multiplier}.
*
* @type {Number}
* @constant
*/
SYSTEM_CLOCK_MULTIPLIER : 1,
/**
* {@link Clock#tick} sets the clock to the current system time;
* ignoring all other settings.
*
* @type {Number}
* @constant
*/
SYSTEM_CLOCK : 2
};
return freezeObject(ClockStep);
});
/*global define*/
define('Core/getTimestamp',[
'./defined'
], function(
defined) {
'use strict';
/*global performance*/
/**
* Gets a timestamp that can be used in measuring the time between events. Timestamps
* are expressed in milliseconds, but it is not specified what the milliseconds are
* measured from. This function uses performance.now() if it is available, or Date.now()
* otherwise.
*
* @exports getTimestamp
*
* @returns {Number} The timestamp in milliseconds since some unspecified reference time.
*/
var getTimestamp;
if (typeof performance !== 'undefined' && defined(performance.now)) {
getTimestamp = function() {
return performance.now();
};
} else {
getTimestamp = function() {
return Date.now();
};
}
return getTimestamp;
});
/*global define*/
define('Core/Clock',[
'./ClockRange',
'./ClockStep',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Event',
'./getTimestamp',
'./JulianDate'
], function(
ClockRange,
ClockStep,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
getTimestamp,
JulianDate) {
'use strict';
/**
* A simple clock for keeping track of simulated time.
*
* @alias Clock
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {JulianDate} [options.startTime] The start time of the clock.
* @param {JulianDate} [options.stopTime] The stop time of the clock.
* @param {JulianDate} [options.currentTime] The current time.
* @param {Number} [options.multiplier=1.0] Determines how much time advances when {@link Clock#tick} is called, negative values allow for advancing backwards.
* @param {ClockStep} [options.clockStep=ClockStep.SYSTEM_CLOCK_MULTIPLIER] Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent.
* @param {ClockRange} [options.clockRange=ClockRange.UNBOUNDED] Determines how the clock should behave when {@link Clock#startTime} or {@link Clock#stopTime} is reached.
* @param {Boolean} [options.canAnimate=true] Indicates whether {@link Clock#tick} can advance time. This could be false if data is being buffered, for example. The clock will only tick when both {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
* @param {Boolean} [options.shouldAnimate=true] Indicates whether {@link Clock#tick} should attempt to advance time. The clock will only tick when both {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
*
* @exception {DeveloperError} startTime must come before stopTime.
*
*
* @example
* // Create a clock that loops on Christmas day 2013 and runs in real-time.
* var clock = new Cesium.Clock({
* startTime : Cesium.JulianDate.fromIso8601("2013-12-25"),
* currentTime : Cesium.JulianDate.fromIso8601("2013-12-25"),
* stopTime : Cesium.JulianDate.fromIso8601("2013-12-26"),
* clockRange : Cesium.ClockRange.LOOP_STOP,
* clockStep : Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER
* });
*
* @see ClockStep
* @see ClockRange
* @see JulianDate
*/
function Clock(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var currentTime = options.currentTime;
var startTime = options.startTime;
var stopTime = options.stopTime;
if (!defined(currentTime)) {
// if not specified, current time is the start time,
// or if that is not specified, 1 day before the stop time,
// or if that is not specified, then now.
if (defined(startTime)) {
currentTime = JulianDate.clone(startTime);
} else if (defined(stopTime)) {
currentTime = JulianDate.addDays(stopTime, -1.0, new JulianDate());
} else {
currentTime = JulianDate.now();
}
} else {
currentTime = JulianDate.clone(currentTime);
}
if (!defined(startTime)) {
// if not specified, start time is the current time
// (as determined above)
startTime = JulianDate.clone(currentTime);
} else {
startTime = JulianDate.clone(startTime);
}
if (!defined(stopTime)) {
// if not specified, stop time is 1 day after the start time
// (as determined above)
stopTime = JulianDate.addDays(startTime, 1.0, new JulianDate());
} else {
stopTime = JulianDate.clone(stopTime);
}
if (JulianDate.greaterThan(startTime, stopTime)) {
throw new DeveloperError('startTime must come before stopTime.');
}
/**
* The start time of the clock.
* @type {JulianDate}
*/
this.startTime = startTime;
/**
* The stop time of the clock.
* @type {JulianDate}
*/
this.stopTime = stopTime;
/**
* Determines how the clock should behave when
* {@link Clock#startTime} or {@link Clock#stopTime}
* is reached.
* @type {ClockRange}
* @default {@link ClockRange.UNBOUNDED}
*/
this.clockRange = defaultValue(options.clockRange, ClockRange.UNBOUNDED);
/**
* Indicates whether {@link Clock#tick} can advance time. This could be false if data is being buffered,
* for example. The clock will only advance time when both
* {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
* @type {Boolean}
* @default true
*/
this.canAnimate = defaultValue(options.canAnimate, true);
/**
* An {@link Event} that is fired whenever {@link Clock#tick} is called.
* @type {Event}
*/
this.onTick = new Event();
this._currentTime = undefined;
this._multiplier = undefined;
this._clockStep = undefined;
this._shouldAnimate = undefined;
this._lastSystemTime = getTimestamp();
// set values using the property setters to
// make values consistent.
this.currentTime = currentTime;
this.multiplier = defaultValue(options.multiplier, 1.0);
this.clockStep = defaultValue(options.clockStep, ClockStep.SYSTEM_CLOCK_MULTIPLIER);
this.shouldAnimate = defaultValue(options.shouldAnimate, true);
}
defineProperties(Clock.prototype, {
/**
* The current time.
* Changing this property will change
* {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
* {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
* @memberof Clock.prototype
* @type {JulianDate}
*/
currentTime : {
get : function() {
return this._currentTime;
},
set : function(value) {
if (JulianDate.equals(this._currentTime, value)) {
return;
}
if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
}
this._currentTime = value;
}
},
/**
* Gets or sets how much time advances when {@link Clock#tick} is called. Negative values allow for advancing backwards.
* If {@link Clock#clockStep} is set to {@link ClockStep.TICK_DEPENDENT}, this is the number of seconds to advance.
* If {@link Clock#clockStep} is set to {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}, this value is multiplied by the
* elapsed system time since the last call to {@link Clock#tick}.
* Changing this property will change
* {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
* {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
* @memberof Clock.prototype
* @type {Number}
* @default 1.0
*/
multiplier : {
get : function() {
return this._multiplier;
},
set : function(value) {
if (this._multiplier === value) {
return;
}
if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
}
this._multiplier = value;
}
},
/**
* Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent.
* Changing this property to {@link ClockStep.SYSTEM_CLOCK} will set
* {@link Clock#multiplier} to 1.0, {@link Clock#shouldAnimate} to true, and
* {@link Clock#currentTime} to the current system clock time.
* @memberof Clock.prototype
* @type ClockStep
* @default {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}
*/
clockStep : {
get : function() {
return this._clockStep;
},
set : function(value) {
if (value === ClockStep.SYSTEM_CLOCK) {
this._multiplier = 1.0;
this._shouldAnimate = true;
this._currentTime = JulianDate.now();
}
this._clockStep = value;
}
},
/**
* Indicates whether {@link Clock#tick} should attempt to advance time.
* The clock will only advance time when both
* {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
* Changing this property will change
* {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
* {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
* @memberof Clock.prototype
* @type {Boolean}
* @default true
*/
shouldAnimate : {
get : function() {
return this._shouldAnimate;
},
set : function(value) {
if (this._shouldAnimate === value) {
return;
}
if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
}
this._shouldAnimate = value;
}
}
});
/**
* Advances the clock from the current time based on the current configuration options.
* tick should be called every frame, regardless of whether animation is taking place
* or not. To control animation, use the {@link Clock#shouldAnimate} property.
*
* @returns {JulianDate} The new value of the {@link Clock#currentTime} property.
*/
Clock.prototype.tick = function() {
var currentSystemTime = getTimestamp();
var currentTime = JulianDate.clone(this._currentTime);
if (this.canAnimate && this._shouldAnimate) {
var clockStep = this._clockStep;
if (clockStep === ClockStep.SYSTEM_CLOCK) {
currentTime = JulianDate.now(currentTime);
} else {
var multiplier = this._multiplier;
if (clockStep === ClockStep.TICK_DEPENDENT) {
currentTime = JulianDate.addSeconds(currentTime, multiplier, currentTime);
} else {
var milliseconds = currentSystemTime - this._lastSystemTime;
currentTime = JulianDate.addSeconds(currentTime, multiplier * (milliseconds / 1000.0), currentTime);
}
var clockRange = this.clockRange;
var startTime = this.startTime;
var stopTime = this.stopTime;
if (clockRange === ClockRange.CLAMPED) {
if (JulianDate.lessThan(currentTime, startTime)) {
currentTime = JulianDate.clone(startTime, currentTime);
} else if (JulianDate.greaterThan(currentTime, stopTime)) {
currentTime = JulianDate.clone(stopTime, currentTime);
}
} else if (clockRange === ClockRange.LOOP_STOP) {
if (JulianDate.lessThan(currentTime, startTime)) {
currentTime = JulianDate.clone(startTime, currentTime);
}
while (JulianDate.greaterThan(currentTime, stopTime)) {
currentTime = JulianDate.addSeconds(startTime, JulianDate.secondsDifference(currentTime, stopTime), currentTime);
}
}
}
}
this._currentTime = currentTime;
this._lastSystemTime = currentSystemTime;
this.onTick.raiseEvent(this);
return currentTime;
};
return Clock;
});
/*global define*/
define('Core/Color',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./FeatureDetection',
'./freezeObject',
'./Math'
], function(
defaultValue,
defined,
DeveloperError,
FeatureDetection,
freezeObject,
CesiumMath) {
'use strict';
function hue2rgb(m1, m2, h) {
if (h < 0) {
h += 1;
}
if (h > 1) {
h -= 1;
}
if (h * 6 < 1) {
return m1 + (m2 - m1) * 6 * h;
}
if (h * 2 < 1) {
return m2;
}
if (h * 3 < 2) {
return m1 + (m2 - m1) * (2 / 3 - h) * 6;
}
return m1;
}
/**
* A color, specified using red, green, blue, and alpha values,
* which range from 0
(no intensity) to 1.0
(full intensity).
* @param {Number} [red=1.0] The red component.
* @param {Number} [green=1.0] The green component.
* @param {Number} [blue=1.0] The blue component.
* @param {Number} [alpha=1.0] The alpha component.
*
* @constructor
* @alias Color
*
* @see Packable
*/
function Color(red, green, blue, alpha) {
/**
* The red component.
* @type {Number}
* @default 1.0
*/
this.red = defaultValue(red, 1.0);
/**
* The green component.
* @type {Number}
* @default 1.0
*/
this.green = defaultValue(green, 1.0);
/**
* The blue component.
* @type {Number}
* @default 1.0
*/
this.blue = defaultValue(blue, 1.0);
/**
* The alpha component.
* @type {Number}
* @default 1.0
*/
this.alpha = defaultValue(alpha, 1.0);
}
/**
* Creates a Color instance from a {@link Cartesian4}. x
, y
, z
,
* and w
map to red
, green
, blue
, and alpha
, respectively.
*
* @param {Cartesian4} cartesian The source cartesian.
* @param {Color} [result] The object onto which to store the result.
* @returns {Color} The modified result parameter or a new Color instance if one was not provided.
*/
Color.fromCartesian4 = function(cartesian, result) {
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
return new Color(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
}
result.red = cartesian.x;
result.green = cartesian.y;
result.blue = cartesian.z;
result.alpha = cartesian.w;
return result;
};
/**
* Creates a new Color specified using red, green, blue, and alpha values
* that are in the range of 0 to 255, converting them internally to a range of 0.0 to 1.0.
*
* @param {Number} [red=255] The red component.
* @param {Number} [green=255] The green component.
* @param {Number} [blue=255] The blue component.
* @param {Number} [alpha=255] The alpha component.
* @param {Color} [result] The object onto which to store the result.
* @returns {Color} The modified result parameter or a new Color instance if one was not provided.
*/
Color.fromBytes = function(red, green, blue, alpha, result) {
red = Color.byteToFloat(defaultValue(red, 255.0));
green = Color.byteToFloat(defaultValue(green, 255.0));
blue = Color.byteToFloat(defaultValue(blue, 255.0));
alpha = Color.byteToFloat(defaultValue(alpha, 255.0));
if (!defined(result)) {
return new Color(red, green, blue, alpha);
}
result.red = red;
result.green = green;
result.blue = blue;
result.alpha = alpha;
return result;
};
/**
* Creates a new Color that has the same red, green, and blue components
* of the specified color, but with the specified alpha value.
*
* @param {Color} color The base color
* @param {Number} alpha The new alpha component.
* @param {Color} [result] The object onto which to store the result.
* @returns {Color} The modified result parameter or a new Color instance if one was not provided.
*
* @example var translucentRed = Cesium.Color.fromAlpha(Cesium.Color.RED, 0.9);
*/
Color.fromAlpha = function(color, alpha, result) {
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(alpha)) {
throw new DeveloperError('alpha is required');
}
if (!defined(result)) {
return new Color(color.red, color.green, color.blue, alpha);
}
result.red = color.red;
result.green = color.green;
result.blue = color.blue;
result.alpha = alpha;
return result;
};
var scratchArrayBuffer;
var scratchUint32Array;
var scratchUint8Array;
if (FeatureDetection.supportsTypedArrays()) {
scratchArrayBuffer = new ArrayBuffer(4);
scratchUint32Array = new Uint32Array(scratchArrayBuffer);
scratchUint8Array = new Uint8Array(scratchArrayBuffer);
}
/**
* Creates a new Color from a single numeric unsigned 32-bit RGBA value, using the endianness
* of the system.
*
* @param {Number} rgba A single numeric unsigned 32-bit RGBA value.
* @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
* @returns {Color} The color object.
*
* @example
* var color = Cesium.Color.fromRgba(0x67ADDFFF);
*
* @see Color#toRgba
*/
Color.fromRgba = function(rgba, result) {
// scratchUint32Array and scratchUint8Array share an underlying array buffer
scratchUint32Array[0] = rgba;
return Color.fromBytes(scratchUint8Array[0], scratchUint8Array[1], scratchUint8Array[2], scratchUint8Array[3], result);
};
/**
* Creates a Color instance from hue, saturation, and lightness.
*
* @param {Number} [hue=0] The hue angle 0...1
* @param {Number} [saturation=0] The saturation value 0...1
* @param {Number} [lightness=0] The lightness value 0...1
* @param {Number} [alpha=1.0] The alpha component 0...1
* @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
* @returns {Color} The color object.
*
* @see {@link http://www.w3.org/TR/css3-color/#hsl-color|CSS color values}
*/
Color.fromHsl = function(hue, saturation, lightness, alpha, result) {
hue = defaultValue(hue, 0.0) % 1.0;
saturation = defaultValue(saturation, 0.0);
lightness = defaultValue(lightness, 0.0);
alpha = defaultValue(alpha, 1.0);
var red = lightness;
var green = lightness;
var blue = lightness;
if (saturation !== 0) {
var m2;
if (lightness < 0.5) {
m2 = lightness * (1 + saturation);
} else {
m2 = lightness + saturation - lightness * saturation;
}
var m1 = 2.0 * lightness - m2;
red = hue2rgb(m1, m2, hue + 1 / 3);
green = hue2rgb(m1, m2, hue);
blue = hue2rgb(m1, m2, hue - 1 / 3);
}
if (!defined(result)) {
return new Color(red, green, blue, alpha);
}
result.red = red;
result.green = green;
result.blue = blue;
result.alpha = alpha;
return result;
};
/**
* Creates a random color using the provided options. For reproducible random colors, you should
* call {@link CesiumMath#setRandomNumberSeed} once at the beginning of your application.
*
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.red] If specified, the red component to use instead of a randomized value.
* @param {Number} [options.minimumRed=0.0] The maximum red value to generate if none was specified.
* @param {Number} [options.maximumRed=1.0] The minimum red value to generate if none was specified.
* @param {Number} [options.green] If specified, the green component to use instead of a randomized value.
* @param {Number} [options.minimumGreen=0.0] The maximum green value to generate if none was specified.
* @param {Number} [options.maximumGreen=1.0] The minimum green value to generate if none was specified.
* @param {Number} [options.blue] If specified, the blue component to use instead of a randomized value.
* @param {Number} [options.minimumBlue=0.0] The maximum blue value to generate if none was specified.
* @param {Number} [options.maximumBlue=1.0] The minimum blue value to generate if none was specified.
* @param {Number} [options.alpha] If specified, the alpha component to use instead of a randomized value.
* @param {Number} [options.minimumAlpha=0.0] The maximum alpha value to generate if none was specified.
* @param {Number} [options.maximumAlpha=1.0] The minimum alpha value to generate if none was specified.
* @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
* @returns {Color} The modified result parameter or a new instance if result was undefined.
*
* @exception {DeveloperError} minimumRed must be less than or equal to maximumRed.
* @exception {DeveloperError} minimumGreen must be less than or equal to maximumGreen.
* @exception {DeveloperError} minimumBlue must be less than or equal to maximumBlue.
* @exception {DeveloperError} minimumAlpha must be less than or equal to maximumAlpha.
*
* @example
* //Create a completely random color
* var color = Cesium.Color.fromRandom();
*
* //Create a random shade of yellow.
* var color = Cesium.Color.fromRandom({
* red : 1.0,
* green : 1.0,
* alpha : 1.0
* });
*
* //Create a random bright color.
* var color = Cesium.Color.fromRandom({
* minimumRed : 0.75,
* minimumGreen : 0.75,
* minimumBlue : 0.75,
* alpha : 1.0
* });
*/
Color.fromRandom = function(options, result) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var red = options.red;
if (!defined(red)) {
var minimumRed = defaultValue(options.minimumRed, 0);
var maximumRed = defaultValue(options.maximumRed, 1.0);
if (minimumRed > maximumRed) {
throw new DeveloperError("minimumRed must be less than or equal to maximumRed");
}
red = minimumRed + (CesiumMath.nextRandomNumber() * (maximumRed - minimumRed));
}
var green = options.green;
if (!defined(green)) {
var minimumGreen = defaultValue(options.minimumGreen, 0);
var maximumGreen = defaultValue(options.maximumGreen, 1.0);
if (minimumGreen > maximumGreen) {
throw new DeveloperError("minimumGreen must be less than or equal to maximumGreen");
}
green = minimumGreen + (CesiumMath.nextRandomNumber() * (maximumGreen - minimumGreen));
}
var blue = options.blue;
if (!defined(blue)) {
var minimumBlue = defaultValue(options.minimumBlue, 0);
var maximumBlue = defaultValue(options.maximumBlue, 1.0);
if (minimumBlue > maximumBlue) {
throw new DeveloperError("minimumBlue must be less than or equal to maximumBlue");
}
blue = minimumBlue + (CesiumMath.nextRandomNumber() * (maximumBlue - minimumBlue));
}
var alpha = options.alpha;
if (!defined(alpha)) {
var minimumAlpha = defaultValue(options.minimumAlpha, 0);
var maximumAlpha = defaultValue(options.maximumAlpha, 1.0);
if (minimumAlpha > maximumAlpha) {
throw new DeveloperError("minimumAlpha must be less than or equal to maximumAlpha");
}
alpha = minimumAlpha + (CesiumMath.nextRandomNumber() * (maximumAlpha - minimumAlpha));
}
if (!defined(result)) {
return new Color(red, green, blue, alpha);
}
result.red = red;
result.green = green;
result.blue = blue;
result.alpha = alpha;
return result;
};
//#rgb
var rgbMatcher = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i;
//#rrggbb
var rrggbbMatcher = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
//rgb(), rgba(), or rgb%()
var rgbParenthesesMatcher = /^rgba?\(\s*([0-9.]+%?)\s*,\s*([0-9.]+%?)\s*,\s*([0-9.]+%?)(?:\s*,\s*([0-9.]+))?\s*\)$/i;
//hsl(), hsla(), or hsl%()
var hslParenthesesMatcher = /^hsla?\(\s*([0-9.]+)\s*,\s*([0-9.]+%)\s*,\s*([0-9.]+%)(?:\s*,\s*([0-9.]+))?\s*\)$/i;
/**
* Creates a Color instance from a CSS color value.
*
* @param {String} color The CSS color value in #rgb, #rrggbb, rgb(), rgba(), hsl(), or hsla() format.
* @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
* @returns {Color} The color object, or undefined if the string was not a valid CSS color.
*
*
* @example
* var cesiumBlue = Cesium.Color.fromCssColorString('#67ADDF');
* var green = Cesium.Color.fromCssColorString('green');
*
* @see {@link http://www.w3.org/TR/css3-color|CSS color values}
*/
Color.fromCssColorString = function(color, result) {
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(result)) {
result = new Color();
}
var namedColor = Color[color.toUpperCase()];
if (defined(namedColor)) {
Color.clone(namedColor, result);
return result;
}
var matches = rgbMatcher.exec(color);
if (matches !== null) {
result.red = parseInt(matches[1], 16) / 15;
result.green = parseInt(matches[2], 16) / 15.0;
result.blue = parseInt(matches[3], 16) / 15.0;
result.alpha = 1.0;
return result;
}
matches = rrggbbMatcher.exec(color);
if (matches !== null) {
result.red = parseInt(matches[1], 16) / 255.0;
result.green = parseInt(matches[2], 16) / 255.0;
result.blue = parseInt(matches[3], 16) / 255.0;
result.alpha = 1.0;
return result;
}
matches = rgbParenthesesMatcher.exec(color);
if (matches !== null) {
result.red = parseFloat(matches[1]) / ('%' === matches[1].substr(-1) ? 100.0 : 255.0);
result.green = parseFloat(matches[2]) / ('%' === matches[2].substr(-1) ? 100.0 : 255.0);
result.blue = parseFloat(matches[3]) / ('%' === matches[3].substr(-1) ? 100.0 : 255.0);
result.alpha = parseFloat(defaultValue(matches[4], '1.0'));
return result;
}
matches = hslParenthesesMatcher.exec(color);
if (matches !== null) {
return Color.fromHsl(parseFloat(matches[1]) / 360.0,
parseFloat(matches[2]) / 100.0,
parseFloat(matches[3]) / 100.0,
parseFloat(defaultValue(matches[4], '1.0')), result);
}
result = undefined;
return result;
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Color.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {Color} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Color.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.red;
array[startingIndex++] = value.green;
array[startingIndex++] = value.blue;
array[startingIndex] = value.alpha;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Color} [result] The object into which to store the result.
* @returns {Color} The modified result parameter or a new Color instance if one was not provided.
*/
Color.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Color();
}
result.red = array[startingIndex++];
result.green = array[startingIndex++];
result.blue = array[startingIndex++];
result.alpha = array[startingIndex];
return result;
};
/**
* Converts a 'byte' color component in the range of 0 to 255 into
* a 'float' color component in the range of 0 to 1.0.
*
* @param {Number} number The number to be converted.
* @returns {Number} The converted number.
*/
Color.byteToFloat = function(number) {
return number / 255.0;
};
/**
* Converts a 'float' color component in the range of 0 to 1.0 into
* a 'byte' color component in the range of 0 to 255.
*
* @param {Number} number The number to be converted.
* @returns {Number} The converted number.
*/
Color.floatToByte = function(number) {
return number === 1.0 ? 255.0 : (number * 256.0) | 0;
};
/**
* Duplicates a Color.
*
* @param {Color} color The Color to duplicate.
* @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
* @returns {Color} The modified result parameter or a new instance if result was undefined. (Returns undefined if color is undefined)
*/
Color.clone = function(color, result) {
if (!defined(color)) {
return undefined;
}
if (!defined(result)) {
return new Color(color.red, color.green, color.blue, color.alpha);
}
result.red = color.red;
result.green = color.green;
result.blue = color.blue;
result.alpha = color.alpha;
return result;
};
/**
* Returns true if the first Color equals the second color.
*
* @param {Color} left The first Color to compare for equality.
* @param {Color} right The second Color to compare for equality.
* @returns {Boolean} true
if the Colors are equal; otherwise, false
.
*/
Color.equals = function(left, right) {
return (left === right) || //
(defined(left) && //
defined(right) && //
left.red === right.red && //
left.green === right.green && //
left.blue === right.blue && //
left.alpha === right.alpha);
};
/**
* @private
*/
Color.equalsArray = function(color, array, offset) {
return color.red === array[offset] &&
color.green === array[offset + 1] &&
color.blue === array[offset + 2] &&
color.alpha === array[offset + 3];
};
/**
* Returns a duplicate of a Color instance.
*
* @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
* @returns {Color} The modified result parameter or a new instance if result was undefined.
*/
Color.prototype.clone = function(result) {
return Color.clone(this, result);
};
/**
* Returns true if this Color equals other.
*
* @param {Color} other The Color to compare for equality.
* @returns {Boolean} true
if the Colors are equal; otherwise, false
.
*/
Color.prototype.equals = function(other) {
return Color.equals(this, other);
};
/**
* Returns true
if this Color equals other componentwise within the specified epsilon.
*
* @param {Color} other The Color to compare for equality.
* @param {Number} [epsilon=0.0] The epsilon to use for equality testing.
* @returns {Boolean} true
if the Colors are equal within the specified epsilon; otherwise, false
.
*/
Color.prototype.equalsEpsilon = function(other, epsilon) {
return (this === other) || //
((defined(other)) && //
(Math.abs(this.red - other.red) <= epsilon) && //
(Math.abs(this.green - other.green) <= epsilon) && //
(Math.abs(this.blue - other.blue) <= epsilon) && //
(Math.abs(this.alpha - other.alpha) <= epsilon));
};
/**
* Creates a string representing this Color in the format '(red, green, blue, alpha)'.
*
* @returns {String} A string representing this Color in the format '(red, green, blue, alpha)'.
*/
Color.prototype.toString = function() {
return '(' + this.red + ', ' + this.green + ', ' + this.blue + ', ' + this.alpha + ')';
};
/**
* Creates a string containing the CSS color value for this color.
*
* @returns {String} The CSS equivalent of this color.
*
* @see {@link http://www.w3.org/TR/css3-color/#rgba-color|CSS RGB or RGBA color values}
*/
Color.prototype.toCssColorString = function() {
var red = Color.floatToByte(this.red);
var green = Color.floatToByte(this.green);
var blue = Color.floatToByte(this.blue);
if (this.alpha === 1) {
return 'rgb(' + red + ',' + green + ',' + blue + ')';
}
return 'rgba(' + red + ',' + green + ',' + blue + ',' + this.alpha + ')';
};
/**
* Converts this color to an array of red, green, blue, and alpha values
* that are in the range of 0 to 255.
*
* @param {Number[]} [result] The array to store the result in, if undefined a new instance will be created.
* @returns {Number[]} The modified result parameter or a new instance if result was undefined.
*/
Color.prototype.toBytes = function(result) {
var red = Color.floatToByte(this.red);
var green = Color.floatToByte(this.green);
var blue = Color.floatToByte(this.blue);
var alpha = Color.floatToByte(this.alpha);
if (!defined(result)) {
return [red, green, blue, alpha];
}
result[0] = red;
result[1] = green;
result[2] = blue;
result[3] = alpha;
return result;
};
/**
* Converts this color to a single numeric unsigned 32-bit RGBA value, using the endianness
* of the system.
*
* @returns {Number} A single numeric unsigned 32-bit RGBA value.
*
*
* @example
* var rgba = Cesium.Color.BLUE.toRgba();
*
* @see Color.fromRgba
*/
Color.prototype.toRgba = function() {
// scratchUint32Array and scratchUint8Array share an underlying array buffer
scratchUint8Array[0] = Color.floatToByte(this.red);
scratchUint8Array[1] = Color.floatToByte(this.green);
scratchUint8Array[2] = Color.floatToByte(this.blue);
scratchUint8Array[3] = Color.floatToByte(this.alpha);
return scratchUint32Array[0];
};
/**
* Brightens this color by the provided magnitude.
*
* @param {Number} magnitude A positive number indicating the amount to brighten.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*
* @example
* var brightBlue = Cesium.Color.BLUE.brighten(0.5, new Cesium.Color());
*/
Color.prototype.brighten = function(magnitude, result) {
if (!defined(magnitude)) {
throw new DeveloperError('magnitude is required.');
}
if (magnitude < 0.0) {
throw new DeveloperError('magnitude must be positive.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
magnitude = (1.0 - magnitude);
result.red = 1.0 - ((1.0 - this.red) * magnitude);
result.green = 1.0 - ((1.0 - this.green) * magnitude);
result.blue = 1.0 - ((1.0 - this.blue) * magnitude);
result.alpha = this.alpha;
return result;
};
/**
* Darkens this color by the provided magnitude.
*
* @param {Number} magnitude A positive number indicating the amount to darken.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*
* @example
* var darkBlue = Cesium.Color.BLUE.darken(0.5, new Cesium.Color());
*/
Color.prototype.darken = function(magnitude, result) {
if (!defined(magnitude)) {
throw new DeveloperError('magnitude is required.');
}
if (magnitude < 0.0) {
throw new DeveloperError('magnitude must be positive.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
magnitude = (1.0 - magnitude);
result.red = this.red * magnitude;
result.green = this.green * magnitude;
result.blue = this.blue * magnitude;
result.alpha = this.alpha;
return result;
};
/**
* Creates a new Color that has the same red, green, and blue components
* as this Color, but with the specified alpha value.
*
* @param {Number} alpha The new alpha component.
* @param {Color} [result] The object onto which to store the result.
* @returns {Color} The modified result parameter or a new Color instance if one was not provided.
*
* @example var translucentRed = Cesium.Color.RED.withAlpha(0.9);
*/
Color.prototype.withAlpha = function(alpha, result) {
return Color.fromAlpha(this, alpha, result);
};
/**
* Computes the componentwise sum of two Colors.
*
* @param {Color} left The first Color.
* @param {Color} right The second Color.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = left.red + right.red;
result.green = left.green + right.green;
result.blue = left.blue + right.blue;
result.alpha = left.alpha + right.alpha;
return result;
};
/**
* Computes the componentwise difference of two Colors.
*
* @param {Color} left The first Color.
* @param {Color} right The second Color.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = left.red - right.red;
result.green = left.green - right.green;
result.blue = left.blue - right.blue;
result.alpha = left.alpha - right.alpha;
return result;
};
/**
* Computes the componentwise product of two Colors.
*
* @param {Color} left The first Color.
* @param {Color} right The second Color.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.multiply = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = left.red * right.red;
result.green = left.green * right.green;
result.blue = left.blue * right.blue;
result.alpha = left.alpha * right.alpha;
return result;
};
/**
* Computes the componentwise quotient of two Colors.
*
* @param {Color} left The first Color.
* @param {Color} right The second Color.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.divide = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = left.red / right.red;
result.green = left.green / right.green;
result.blue = left.blue / right.blue;
result.alpha = left.alpha / right.alpha;
return result;
};
/**
* Computes the componentwise modulus of two Colors.
*
* @param {Color} left The first Color.
* @param {Color} right The second Color.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.mod = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = left.red % right.red;
result.green = left.green % right.green;
result.blue = left.blue % right.blue;
result.alpha = left.alpha % right.alpha;
return result;
};
/**
* Multiplies the provided Color componentwise by the provided scalar.
*
* @param {Color} color The Color to be scaled.
* @param {Number} scalar The scalar to multiply with.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.multiplyByScalar = function(color, scalar, result) {
if (!defined(color)) {
throw new DeveloperError('cartesian is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = color.red * scalar;
result.green = color.green * scalar;
result.blue = color.blue * scalar;
result.alpha = color.alpha * scalar;
return result;
};
/**
* Divides the provided Color componentwise by the provided scalar.
*
* @param {Color} color The Color to be divided.
* @param {Number} scalar The scalar to divide with.
* @param {Color} result The object onto which to store the result.
* @returns {Color} The modified result parameter.
*/
Color.divideByScalar = function(color, scalar, result) {
if (!defined(color)) {
throw new DeveloperError('cartesian is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.red = color.red / scalar;
result.green = color.green / scalar;
result.blue = color.blue / scalar;
result.alpha = color.alpha / scalar;
return result;
};
/**
* An immutable Color instance initialized to CSS color #F0F8FF
*
*
* @constant
* @type {Color}
*/
Color.ALICEBLUE = freezeObject(Color.fromCssColorString('#F0F8FF'));
/**
* An immutable Color instance initialized to CSS color #FAEBD7
*
*
* @constant
* @type {Color}
*/
Color.ANTIQUEWHITE = freezeObject(Color.fromCssColorString('#FAEBD7'));
/**
* An immutable Color instance initialized to CSS color #00FFFF
*
*
* @constant
* @type {Color}
*/
Color.AQUA = freezeObject(Color.fromCssColorString('#00FFFF'));
/**
* An immutable Color instance initialized to CSS color #7FFFD4
*
*
* @constant
* @type {Color}
*/
Color.AQUAMARINE = freezeObject(Color.fromCssColorString('#7FFFD4'));
/**
* An immutable Color instance initialized to CSS color #F0FFFF
*
*
* @constant
* @type {Color}
*/
Color.AZURE = freezeObject(Color.fromCssColorString('#F0FFFF'));
/**
* An immutable Color instance initialized to CSS color #F5F5DC
*
*
* @constant
* @type {Color}
*/
Color.BEIGE = freezeObject(Color.fromCssColorString('#F5F5DC'));
/**
* An immutable Color instance initialized to CSS color #FFE4C4
*
*
* @constant
* @type {Color}
*/
Color.BISQUE = freezeObject(Color.fromCssColorString('#FFE4C4'));
/**
* An immutable Color instance initialized to CSS color #000000
*
*
* @constant
* @type {Color}
*/
Color.BLACK = freezeObject(Color.fromCssColorString('#000000'));
/**
* An immutable Color instance initialized to CSS color #FFEBCD
*
*
* @constant
* @type {Color}
*/
Color.BLANCHEDALMOND = freezeObject(Color.fromCssColorString('#FFEBCD'));
/**
* An immutable Color instance initialized to CSS color #0000FF
*
*
* @constant
* @type {Color}
*/
Color.BLUE = freezeObject(Color.fromCssColorString('#0000FF'));
/**
* An immutable Color instance initialized to CSS color #8A2BE2
*
*
* @constant
* @type {Color}
*/
Color.BLUEVIOLET = freezeObject(Color.fromCssColorString('#8A2BE2'));
/**
* An immutable Color instance initialized to CSS color #A52A2A
*
*
* @constant
* @type {Color}
*/
Color.BROWN = freezeObject(Color.fromCssColorString('#A52A2A'));
/**
* An immutable Color instance initialized to CSS color #DEB887
*
*
* @constant
* @type {Color}
*/
Color.BURLYWOOD = freezeObject(Color.fromCssColorString('#DEB887'));
/**
* An immutable Color instance initialized to CSS color #5F9EA0
*
*
* @constant
* @type {Color}
*/
Color.CADETBLUE = freezeObject(Color.fromCssColorString('#5F9EA0'));
/**
* An immutable Color instance initialized to CSS color #7FFF00
*
*
* @constant
* @type {Color}
*/
Color.CHARTREUSE = freezeObject(Color.fromCssColorString('#7FFF00'));
/**
* An immutable Color instance initialized to CSS color #D2691E
*
*
* @constant
* @type {Color}
*/
Color.CHOCOLATE = freezeObject(Color.fromCssColorString('#D2691E'));
/**
* An immutable Color instance initialized to CSS color #FF7F50
*
*
* @constant
* @type {Color}
*/
Color.CORAL = freezeObject(Color.fromCssColorString('#FF7F50'));
/**
* An immutable Color instance initialized to CSS color #6495ED
*
*
* @constant
* @type {Color}
*/
Color.CORNFLOWERBLUE = freezeObject(Color.fromCssColorString('#6495ED'));
/**
* An immutable Color instance initialized to CSS color #FFF8DC
*
*
* @constant
* @type {Color}
*/
Color.CORNSILK = freezeObject(Color.fromCssColorString('#FFF8DC'));
/**
* An immutable Color instance initialized to CSS color #DC143C
*
*
* @constant
* @type {Color}
*/
Color.CRIMSON = freezeObject(Color.fromCssColorString('#DC143C'));
/**
* An immutable Color instance initialized to CSS color #00FFFF
*
*
* @constant
* @type {Color}
*/
Color.CYAN = freezeObject(Color.fromCssColorString('#00FFFF'));
/**
* An immutable Color instance initialized to CSS color #00008B
*
*
* @constant
* @type {Color}
*/
Color.DARKBLUE = freezeObject(Color.fromCssColorString('#00008B'));
/**
* An immutable Color instance initialized to CSS color #008B8B
*
*
* @constant
* @type {Color}
*/
Color.DARKCYAN = freezeObject(Color.fromCssColorString('#008B8B'));
/**
* An immutable Color instance initialized to CSS color #B8860B
*
*
* @constant
* @type {Color}
*/
Color.DARKGOLDENROD = freezeObject(Color.fromCssColorString('#B8860B'));
/**
* An immutable Color instance initialized to CSS color #A9A9A9
*
*
* @constant
* @type {Color}
*/
Color.DARKGRAY = freezeObject(Color.fromCssColorString('#A9A9A9'));
/**
* An immutable Color instance initialized to CSS color #006400
*
*
* @constant
* @type {Color}
*/
Color.DARKGREEN = freezeObject(Color.fromCssColorString('#006400'));
/**
* An immutable Color instance initialized to CSS color #A9A9A9
*
*
* @constant
* @type {Color}
*/
Color.DARKGREY = Color.DARKGRAY;
/**
* An immutable Color instance initialized to CSS color #BDB76B
*
*
* @constant
* @type {Color}
*/
Color.DARKKHAKI = freezeObject(Color.fromCssColorString('#BDB76B'));
/**
* An immutable Color instance initialized to CSS color #8B008B
*
*
* @constant
* @type {Color}
*/
Color.DARKMAGENTA = freezeObject(Color.fromCssColorString('#8B008B'));
/**
* An immutable Color instance initialized to CSS color #556B2F
*
*
* @constant
* @type {Color}
*/
Color.DARKOLIVEGREEN = freezeObject(Color.fromCssColorString('#556B2F'));
/**
* An immutable Color instance initialized to CSS color #FF8C00
*
*
* @constant
* @type {Color}
*/
Color.DARKORANGE = freezeObject(Color.fromCssColorString('#FF8C00'));
/**
* An immutable Color instance initialized to CSS color #9932CC
*
*
* @constant
* @type {Color}
*/
Color.DARKORCHID = freezeObject(Color.fromCssColorString('#9932CC'));
/**
* An immutable Color instance initialized to CSS color #8B0000
*
*
* @constant
* @type {Color}
*/
Color.DARKRED = freezeObject(Color.fromCssColorString('#8B0000'));
/**
* An immutable Color instance initialized to CSS color #E9967A
*
*
* @constant
* @type {Color}
*/
Color.DARKSALMON = freezeObject(Color.fromCssColorString('#E9967A'));
/**
* An immutable Color instance initialized to CSS color #8FBC8F
*
*
* @constant
* @type {Color}
*/
Color.DARKSEAGREEN = freezeObject(Color.fromCssColorString('#8FBC8F'));
/**
* An immutable Color instance initialized to CSS color #483D8B
*
*
* @constant
* @type {Color}
*/
Color.DARKSLATEBLUE = freezeObject(Color.fromCssColorString('#483D8B'));
/**
* An immutable Color instance initialized to CSS color #2F4F4F
*
*
* @constant
* @type {Color}
*/
Color.DARKSLATEGRAY = freezeObject(Color.fromCssColorString('#2F4F4F'));
/**
* An immutable Color instance initialized to CSS color #2F4F4F
*
*
* @constant
* @type {Color}
*/
Color.DARKSLATEGREY = Color.DARKSLATEGRAY;
/**
* An immutable Color instance initialized to CSS color #00CED1
*
*
* @constant
* @type {Color}
*/
Color.DARKTURQUOISE = freezeObject(Color.fromCssColorString('#00CED1'));
/**
* An immutable Color instance initialized to CSS color #9400D3
*
*
* @constant
* @type {Color}
*/
Color.DARKVIOLET = freezeObject(Color.fromCssColorString('#9400D3'));
/**
* An immutable Color instance initialized to CSS color #FF1493
*
*
* @constant
* @type {Color}
*/
Color.DEEPPINK = freezeObject(Color.fromCssColorString('#FF1493'));
/**
* An immutable Color instance initialized to CSS color #00BFFF
*
*
* @constant
* @type {Color}
*/
Color.DEEPSKYBLUE = freezeObject(Color.fromCssColorString('#00BFFF'));
/**
* An immutable Color instance initialized to CSS color #696969
*
*
* @constant
* @type {Color}
*/
Color.DIMGRAY = freezeObject(Color.fromCssColorString('#696969'));
/**
* An immutable Color instance initialized to CSS color #696969
*
*
* @constant
* @type {Color}
*/
Color.DIMGREY = Color.DIMGRAY;
/**
* An immutable Color instance initialized to CSS color #1E90FF
*
*
* @constant
* @type {Color}
*/
Color.DODGERBLUE = freezeObject(Color.fromCssColorString('#1E90FF'));
/**
* An immutable Color instance initialized to CSS color #B22222
*
*
* @constant
* @type {Color}
*/
Color.FIREBRICK = freezeObject(Color.fromCssColorString('#B22222'));
/**
* An immutable Color instance initialized to CSS color #FFFAF0
*
*
* @constant
* @type {Color}
*/
Color.FLORALWHITE = freezeObject(Color.fromCssColorString('#FFFAF0'));
/**
* An immutable Color instance initialized to CSS color #228B22
*
*
* @constant
* @type {Color}
*/
Color.FORESTGREEN = freezeObject(Color.fromCssColorString('#228B22'));
/**
* An immutable Color instance initialized to CSS color #FF00FF
*
*
* @constant
* @type {Color}
*/
Color.FUSCHIA = freezeObject(Color.fromCssColorString('#FF00FF'));
/**
* An immutable Color instance initialized to CSS color #DCDCDC
*
*
* @constant
* @type {Color}
*/
Color.GAINSBORO = freezeObject(Color.fromCssColorString('#DCDCDC'));
/**
* An immutable Color instance initialized to CSS color #F8F8FF
*
*
* @constant
* @type {Color}
*/
Color.GHOSTWHITE = freezeObject(Color.fromCssColorString('#F8F8FF'));
/**
* An immutable Color instance initialized to CSS color #FFD700
*
*
* @constant
* @type {Color}
*/
Color.GOLD = freezeObject(Color.fromCssColorString('#FFD700'));
/**
* An immutable Color instance initialized to CSS color #DAA520
*
*
* @constant
* @type {Color}
*/
Color.GOLDENROD = freezeObject(Color.fromCssColorString('#DAA520'));
/**
* An immutable Color instance initialized to CSS color #808080
*
*
* @constant
* @type {Color}
*/
Color.GRAY = freezeObject(Color.fromCssColorString('#808080'));
/**
* An immutable Color instance initialized to CSS color #008000
*
*
* @constant
* @type {Color}
*/
Color.GREEN = freezeObject(Color.fromCssColorString('#008000'));
/**
* An immutable Color instance initialized to CSS color #ADFF2F
*
*
* @constant
* @type {Color}
*/
Color.GREENYELLOW = freezeObject(Color.fromCssColorString('#ADFF2F'));
/**
* An immutable Color instance initialized to CSS color #808080
*
*
* @constant
* @type {Color}
*/
Color.GREY = Color.GRAY;
/**
* An immutable Color instance initialized to CSS color #F0FFF0
*
*
* @constant
* @type {Color}
*/
Color.HONEYDEW = freezeObject(Color.fromCssColorString('#F0FFF0'));
/**
* An immutable Color instance initialized to CSS color #FF69B4
*
*
* @constant
* @type {Color}
*/
Color.HOTPINK = freezeObject(Color.fromCssColorString('#FF69B4'));
/**
* An immutable Color instance initialized to CSS color #CD5C5C
*
*
* @constant
* @type {Color}
*/
Color.INDIANRED = freezeObject(Color.fromCssColorString('#CD5C5C'));
/**
* An immutable Color instance initialized to CSS color #4B0082
*
*
* @constant
* @type {Color}
*/
Color.INDIGO = freezeObject(Color.fromCssColorString('#4B0082'));
/**
* An immutable Color instance initialized to CSS color #FFFFF0
*
*
* @constant
* @type {Color}
*/
Color.IVORY = freezeObject(Color.fromCssColorString('#FFFFF0'));
/**
* An immutable Color instance initialized to CSS color #F0E68C
*
*
* @constant
* @type {Color}
*/
Color.KHAKI = freezeObject(Color.fromCssColorString('#F0E68C'));
/**
* An immutable Color instance initialized to CSS color #E6E6FA
*
*
* @constant
* @type {Color}
*/
Color.LAVENDER = freezeObject(Color.fromCssColorString('#E6E6FA'));
/**
* An immutable Color instance initialized to CSS color #FFF0F5
*
*
* @constant
* @type {Color}
*/
Color.LAVENDAR_BLUSH = freezeObject(Color.fromCssColorString('#FFF0F5'));
/**
* An immutable Color instance initialized to CSS color #7CFC00
*
*
* @constant
* @type {Color}
*/
Color.LAWNGREEN = freezeObject(Color.fromCssColorString('#7CFC00'));
/**
* An immutable Color instance initialized to CSS color #FFFACD
*
*
* @constant
* @type {Color}
*/
Color.LEMONCHIFFON = freezeObject(Color.fromCssColorString('#FFFACD'));
/**
* An immutable Color instance initialized to CSS color #ADD8E6
*
*
* @constant
* @type {Color}
*/
Color.LIGHTBLUE = freezeObject(Color.fromCssColorString('#ADD8E6'));
/**
* An immutable Color instance initialized to CSS color #F08080
*
*
* @constant
* @type {Color}
*/
Color.LIGHTCORAL = freezeObject(Color.fromCssColorString('#F08080'));
/**
* An immutable Color instance initialized to CSS color #E0FFFF
*
*
* @constant
* @type {Color}
*/
Color.LIGHTCYAN = freezeObject(Color.fromCssColorString('#E0FFFF'));
/**
* An immutable Color instance initialized to CSS color #FAFAD2
*
*
* @constant
* @type {Color}
*/
Color.LIGHTGOLDENRODYELLOW = freezeObject(Color.fromCssColorString('#FAFAD2'));
/**
* An immutable Color instance initialized to CSS color #D3D3D3
*
*
* @constant
* @type {Color}
*/
Color.LIGHTGRAY = freezeObject(Color.fromCssColorString('#D3D3D3'));
/**
* An immutable Color instance initialized to CSS color #90EE90
*
*
* @constant
* @type {Color}
*/
Color.LIGHTGREEN = freezeObject(Color.fromCssColorString('#90EE90'));
/**
* An immutable Color instance initialized to CSS color #D3D3D3
*
*
* @constant
* @type {Color}
*/
Color.LIGHTGREY = Color.LIGHTGRAY;
/**
* An immutable Color instance initialized to CSS color #FFB6C1
*
*
* @constant
* @type {Color}
*/
Color.LIGHTPINK = freezeObject(Color.fromCssColorString('#FFB6C1'));
/**
* An immutable Color instance initialized to CSS color #20B2AA
*
*
* @constant
* @type {Color}
*/
Color.LIGHTSEAGREEN = freezeObject(Color.fromCssColorString('#20B2AA'));
/**
* An immutable Color instance initialized to CSS color #87CEFA
*
*
* @constant
* @type {Color}
*/
Color.LIGHTSKYBLUE = freezeObject(Color.fromCssColorString('#87CEFA'));
/**
* An immutable Color instance initialized to CSS color #778899
*
*
* @constant
* @type {Color}
*/
Color.LIGHTSLATEGRAY = freezeObject(Color.fromCssColorString('#778899'));
/**
* An immutable Color instance initialized to CSS color #778899
*
*
* @constant
* @type {Color}
*/
Color.LIGHTSLATEGREY = Color.LIGHTSLATEGRAY;
/**
* An immutable Color instance initialized to CSS color #B0C4DE
*
*
* @constant
* @type {Color}
*/
Color.LIGHTSTEELBLUE = freezeObject(Color.fromCssColorString('#B0C4DE'));
/**
* An immutable Color instance initialized to CSS color #FFFFE0
*
*
* @constant
* @type {Color}
*/
Color.LIGHTYELLOW = freezeObject(Color.fromCssColorString('#FFFFE0'));
/**
* An immutable Color instance initialized to CSS color #00FF00
*
*
* @constant
* @type {Color}
*/
Color.LIME = freezeObject(Color.fromCssColorString('#00FF00'));
/**
* An immutable Color instance initialized to CSS color #32CD32
*
*
* @constant
* @type {Color}
*/
Color.LIMEGREEN = freezeObject(Color.fromCssColorString('#32CD32'));
/**
* An immutable Color instance initialized to CSS color #FAF0E6
*
*
* @constant
* @type {Color}
*/
Color.LINEN = freezeObject(Color.fromCssColorString('#FAF0E6'));
/**
* An immutable Color instance initialized to CSS color #FF00FF
*
*
* @constant
* @type {Color}
*/
Color.MAGENTA = freezeObject(Color.fromCssColorString('#FF00FF'));
/**
* An immutable Color instance initialized to CSS color #800000
*
*
* @constant
* @type {Color}
*/
Color.MAROON = freezeObject(Color.fromCssColorString('#800000'));
/**
* An immutable Color instance initialized to CSS color #66CDAA
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMAQUAMARINE = freezeObject(Color.fromCssColorString('#66CDAA'));
/**
* An immutable Color instance initialized to CSS color #0000CD
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMBLUE = freezeObject(Color.fromCssColorString('#0000CD'));
/**
* An immutable Color instance initialized to CSS color #BA55D3
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMORCHID = freezeObject(Color.fromCssColorString('#BA55D3'));
/**
* An immutable Color instance initialized to CSS color #9370DB
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMPURPLE = freezeObject(Color.fromCssColorString('#9370DB'));
/**
* An immutable Color instance initialized to CSS color #3CB371
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMSEAGREEN = freezeObject(Color.fromCssColorString('#3CB371'));
/**
* An immutable Color instance initialized to CSS color #7B68EE
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMSLATEBLUE = freezeObject(Color.fromCssColorString('#7B68EE'));
/**
* An immutable Color instance initialized to CSS color #00FA9A
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMSPRINGGREEN = freezeObject(Color.fromCssColorString('#00FA9A'));
/**
* An immutable Color instance initialized to CSS color #48D1CC
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMTURQUOISE = freezeObject(Color.fromCssColorString('#48D1CC'));
/**
* An immutable Color instance initialized to CSS color #C71585
*
*
* @constant
* @type {Color}
*/
Color.MEDIUMVIOLETRED = freezeObject(Color.fromCssColorString('#C71585'));
/**
* An immutable Color instance initialized to CSS color #191970
*
*
* @constant
* @type {Color}
*/
Color.MIDNIGHTBLUE = freezeObject(Color.fromCssColorString('#191970'));
/**
* An immutable Color instance initialized to CSS color #F5FFFA
*
*
* @constant
* @type {Color}
*/
Color.MINTCREAM = freezeObject(Color.fromCssColorString('#F5FFFA'));
/**
* An immutable Color instance initialized to CSS color #FFE4E1
*
*
* @constant
* @type {Color}
*/
Color.MISTYROSE = freezeObject(Color.fromCssColorString('#FFE4E1'));
/**
* An immutable Color instance initialized to CSS color #FFE4B5
*
*
* @constant
* @type {Color}
*/
Color.MOCCASIN = freezeObject(Color.fromCssColorString('#FFE4B5'));
/**
* An immutable Color instance initialized to CSS color #FFDEAD
*
*
* @constant
* @type {Color}
*/
Color.NAVAJOWHITE = freezeObject(Color.fromCssColorString('#FFDEAD'));
/**
* An immutable Color instance initialized to CSS color #000080
*
*
* @constant
* @type {Color}
*/
Color.NAVY = freezeObject(Color.fromCssColorString('#000080'));
/**
* An immutable Color instance initialized to CSS color #FDF5E6
*
*
* @constant
* @type {Color}
*/
Color.OLDLACE = freezeObject(Color.fromCssColorString('#FDF5E6'));
/**
* An immutable Color instance initialized to CSS color #808000
*
*
* @constant
* @type {Color}
*/
Color.OLIVE = freezeObject(Color.fromCssColorString('#808000'));
/**
* An immutable Color instance initialized to CSS color #6B8E23
*
*
* @constant
* @type {Color}
*/
Color.OLIVEDRAB = freezeObject(Color.fromCssColorString('#6B8E23'));
/**
* An immutable Color instance initialized to CSS color #FFA500
*
*
* @constant
* @type {Color}
*/
Color.ORANGE = freezeObject(Color.fromCssColorString('#FFA500'));
/**
* An immutable Color instance initialized to CSS color #FF4500
*
*
* @constant
* @type {Color}
*/
Color.ORANGERED = freezeObject(Color.fromCssColorString('#FF4500'));
/**
* An immutable Color instance initialized to CSS color #DA70D6
*
*
* @constant
* @type {Color}
*/
Color.ORCHID = freezeObject(Color.fromCssColorString('#DA70D6'));
/**
* An immutable Color instance initialized to CSS color #EEE8AA
*
*
* @constant
* @type {Color}
*/
Color.PALEGOLDENROD = freezeObject(Color.fromCssColorString('#EEE8AA'));
/**
* An immutable Color instance initialized to CSS color #98FB98
*
*
* @constant
* @type {Color}
*/
Color.PALEGREEN = freezeObject(Color.fromCssColorString('#98FB98'));
/**
* An immutable Color instance initialized to CSS color #AFEEEE
*
*
* @constant
* @type {Color}
*/
Color.PALETURQUOISE = freezeObject(Color.fromCssColorString('#AFEEEE'));
/**
* An immutable Color instance initialized to CSS color #DB7093
*
*
* @constant
* @type {Color}
*/
Color.PALEVIOLETRED = freezeObject(Color.fromCssColorString('#DB7093'));
/**
* An immutable Color instance initialized to CSS color #FFEFD5
*
*
* @constant
* @type {Color}
*/
Color.PAPAYAWHIP = freezeObject(Color.fromCssColorString('#FFEFD5'));
/**
* An immutable Color instance initialized to CSS color #FFDAB9
*
*
* @constant
* @type {Color}
*/
Color.PEACHPUFF = freezeObject(Color.fromCssColorString('#FFDAB9'));
/**
* An immutable Color instance initialized to CSS color #CD853F
*
*
* @constant
* @type {Color}
*/
Color.PERU = freezeObject(Color.fromCssColorString('#CD853F'));
/**
* An immutable Color instance initialized to CSS color #FFC0CB
*
*
* @constant
* @type {Color}
*/
Color.PINK = freezeObject(Color.fromCssColorString('#FFC0CB'));
/**
* An immutable Color instance initialized to CSS color #DDA0DD
*
*
* @constant
* @type {Color}
*/
Color.PLUM = freezeObject(Color.fromCssColorString('#DDA0DD'));
/**
* An immutable Color instance initialized to CSS color #B0E0E6
*
*
* @constant
* @type {Color}
*/
Color.POWDERBLUE = freezeObject(Color.fromCssColorString('#B0E0E6'));
/**
* An immutable Color instance initialized to CSS color #800080
*
*
* @constant
* @type {Color}
*/
Color.PURPLE = freezeObject(Color.fromCssColorString('#800080'));
/**
* An immutable Color instance initialized to CSS color #FF0000
*
*
* @constant
* @type {Color}
*/
Color.RED = freezeObject(Color.fromCssColorString('#FF0000'));
/**
* An immutable Color instance initialized to CSS color #BC8F8F
*
*
* @constant
* @type {Color}
*/
Color.ROSYBROWN = freezeObject(Color.fromCssColorString('#BC8F8F'));
/**
* An immutable Color instance initialized to CSS color #4169E1
*
*
* @constant
* @type {Color}
*/
Color.ROYALBLUE = freezeObject(Color.fromCssColorString('#4169E1'));
/**
* An immutable Color instance initialized to CSS color #8B4513
*
*
* @constant
* @type {Color}
*/
Color.SADDLEBROWN = freezeObject(Color.fromCssColorString('#8B4513'));
/**
* An immutable Color instance initialized to CSS color #FA8072
*
*
* @constant
* @type {Color}
*/
Color.SALMON = freezeObject(Color.fromCssColorString('#FA8072'));
/**
* An immutable Color instance initialized to CSS color #F4A460
*
*
* @constant
* @type {Color}
*/
Color.SANDYBROWN = freezeObject(Color.fromCssColorString('#F4A460'));
/**
* An immutable Color instance initialized to CSS color #2E8B57
*
*
* @constant
* @type {Color}
*/
Color.SEAGREEN = freezeObject(Color.fromCssColorString('#2E8B57'));
/**
* An immutable Color instance initialized to CSS color #FFF5EE
*
*
* @constant
* @type {Color}
*/
Color.SEASHELL = freezeObject(Color.fromCssColorString('#FFF5EE'));
/**
* An immutable Color instance initialized to CSS color #A0522D
*
*
* @constant
* @type {Color}
*/
Color.SIENNA = freezeObject(Color.fromCssColorString('#A0522D'));
/**
* An immutable Color instance initialized to CSS color #C0C0C0
*
*
* @constant
* @type {Color}
*/
Color.SILVER = freezeObject(Color.fromCssColorString('#C0C0C0'));
/**
* An immutable Color instance initialized to CSS color #87CEEB
*
*
* @constant
* @type {Color}
*/
Color.SKYBLUE = freezeObject(Color.fromCssColorString('#87CEEB'));
/**
* An immutable Color instance initialized to CSS color #6A5ACD
*
*
* @constant
* @type {Color}
*/
Color.SLATEBLUE = freezeObject(Color.fromCssColorString('#6A5ACD'));
/**
* An immutable Color instance initialized to CSS color #708090
*
*
* @constant
* @type {Color}
*/
Color.SLATEGRAY = freezeObject(Color.fromCssColorString('#708090'));
/**
* An immutable Color instance initialized to CSS color #708090
*
*
* @constant
* @type {Color}
*/
Color.SLATEGREY = Color.SLATEGRAY;
/**
* An immutable Color instance initialized to CSS color #FFFAFA
*
*
* @constant
* @type {Color}
*/
Color.SNOW = freezeObject(Color.fromCssColorString('#FFFAFA'));
/**
* An immutable Color instance initialized to CSS color #00FF7F
*
*
* @constant
* @type {Color}
*/
Color.SPRINGGREEN = freezeObject(Color.fromCssColorString('#00FF7F'));
/**
* An immutable Color instance initialized to CSS color #4682B4
*
*
* @constant
* @type {Color}
*/
Color.STEELBLUE = freezeObject(Color.fromCssColorString('#4682B4'));
/**
* An immutable Color instance initialized to CSS color #D2B48C
*
*
* @constant
* @type {Color}
*/
Color.TAN = freezeObject(Color.fromCssColorString('#D2B48C'));
/**
* An immutable Color instance initialized to CSS color #008080
*
*
* @constant
* @type {Color}
*/
Color.TEAL = freezeObject(Color.fromCssColorString('#008080'));
/**
* An immutable Color instance initialized to CSS color #D8BFD8
*
*
* @constant
* @type {Color}
*/
Color.THISTLE = freezeObject(Color.fromCssColorString('#D8BFD8'));
/**
* An immutable Color instance initialized to CSS color #FF6347
*
*
* @constant
* @type {Color}
*/
Color.TOMATO = freezeObject(Color.fromCssColorString('#FF6347'));
/**
* An immutable Color instance initialized to CSS color #40E0D0
*
*
* @constant
* @type {Color}
*/
Color.TURQUOISE = freezeObject(Color.fromCssColorString('#40E0D0'));
/**
* An immutable Color instance initialized to CSS color #EE82EE
*
*
* @constant
* @type {Color}
*/
Color.VIOLET = freezeObject(Color.fromCssColorString('#EE82EE'));
/**
* An immutable Color instance initialized to CSS color #F5DEB3
*
*
* @constant
* @type {Color}
*/
Color.WHEAT = freezeObject(Color.fromCssColorString('#F5DEB3'));
/**
* An immutable Color instance initialized to CSS color #FFFFFF
*
*
* @constant
* @type {Color}
*/
Color.WHITE = freezeObject(Color.fromCssColorString('#FFFFFF'));
/**
* An immutable Color instance initialized to CSS color #F5F5F5
*
*
* @constant
* @type {Color}
*/
Color.WHITESMOKE = freezeObject(Color.fromCssColorString('#F5F5F5'));
/**
* An immutable Color instance initialized to CSS color #FFFF00
*
*
* @constant
* @type {Color}
*/
Color.YELLOW = freezeObject(Color.fromCssColorString('#FFFF00'));
/**
* An immutable Color instance initialized to CSS color #9ACD32
*
*
* @constant
* @type {Color}
*/
Color.YELLOWGREEN = freezeObject(Color.fromCssColorString('#9ACD32'));
/**
* An immutable Color instance initialized to CSS transparent.
*
*
* @constant
* @type {Color}
*/
Color.TRANSPARENT = freezeObject(new Color(0, 0, 0, 0));
return Color;
});
/*global define*/
define('Core/ColorGeometryInstanceAttribute',[
'./Color',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError'
], function(
Color,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* Value and type information for per-instance geometry color.
*
* @alias ColorGeometryInstanceAttribute
* @constructor
*
* @param {Number} [red=1.0] The red component.
* @param {Number} [green=1.0] The green component.
* @param {Number} [blue=1.0] The blue component.
* @param {Number} [alpha=1.0] The alpha component.
*
*
* @example
* var instance = new Cesium.GeometryInstance({
* geometry : Cesium.BoxGeometry.fromDimensions({
* dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 500000.0)
* }),
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(0.0, 0.0)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
* id : 'box',
* attributes : {
* color : new Cesium.ColorGeometryInstanceAttribute(red, green, blue, alpha)
* }
* });
*
* @see GeometryInstance
* @see GeometryInstanceAttribute
*/
function ColorGeometryInstanceAttribute(red, green, blue, alpha) {
red = defaultValue(red, 1.0);
green = defaultValue(green, 1.0);
blue = defaultValue(blue, 1.0);
alpha = defaultValue(alpha, 1.0);
/**
* The values for the attributes stored in a typed array.
*
* @type Uint8Array
*
* @default [255, 255, 255, 255]
*/
this.value = new Uint8Array([
Color.floatToByte(red),
Color.floatToByte(green),
Color.floatToByte(blue),
Color.floatToByte(alpha)
]);
}
defineProperties(ColorGeometryInstanceAttribute.prototype, {
/**
* The datatype of each component in the attribute, e.g., individual elements in
* {@link ColorGeometryInstanceAttribute#value}.
*
* @memberof ColorGeometryInstanceAttribute.prototype
*
* @type {ComponentDatatype}
* @readonly
*
* @default {@link ComponentDatatype.UNSIGNED_BYTE}
*/
componentDatatype : {
get : function() {
return ComponentDatatype.UNSIGNED_BYTE;
}
},
/**
* The number of components in the attributes, i.e., {@link ColorGeometryInstanceAttribute#value}.
*
* @memberof ColorGeometryInstanceAttribute.prototype
*
* @type {Number}
* @readonly
*
* @default 4
*/
componentsPerAttribute : {
get : function() {
return 4;
}
},
/**
* When true
and componentDatatype
is an integer format,
* indicate that the components should be mapped to the range [0, 1] (unsigned)
* or [-1, 1] (signed) when they are accessed as floating-point for rendering.
*
* @memberof ColorGeometryInstanceAttribute.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
normalize : {
get : function() {
return true;
}
}
});
/**
* Creates a new {@link ColorGeometryInstanceAttribute} instance given the provided {@link Color}.
*
* @param {Color} color The color.
* @returns {ColorGeometryInstanceAttribute} The new {@link ColorGeometryInstanceAttribute} instance.
*
* @example
* var instance = new Cesium.GeometryInstance({
* geometry : geometry,
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.CORNFLOWERBLUE),
* }
* });
*/
ColorGeometryInstanceAttribute.fromColor = function(color) {
if (!defined(color)) {
throw new DeveloperError('color is required.');
}
return new ColorGeometryInstanceAttribute(color.red, color.green, color.blue, color.alpha);
};
/**
* Converts a color to a typed array that can be used to assign a color attribute.
*
* @param {Color} color The color.
* @param {Uint8Array} [result] The array to store the result in, if undefined a new instance will be created.
*
* @returns {Uint8Array} The modified result parameter or a new instance if result was undefined.
*
* @example
* var attributes = primitive.getGeometryInstanceAttributes('an id');
* attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA, attributes.color);
*/
ColorGeometryInstanceAttribute.toValue = function(color, result) {
if (!defined(color)) {
throw new DeveloperError('color is required.');
}
if (!defined(result)) {
return new Uint8Array(color.toBytes());
}
return color.toBytes(result);
};
/**
* Compares the provided ColorGeometryInstanceAttributes and returns
* true
if they are equal, false
otherwise.
*
* @param {ColorGeometryInstanceAttribute} [left] The first ColorGeometryInstanceAttribute.
* @param {ColorGeometryInstanceAttribute} [right] The second ColorGeometryInstanceAttribute.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
ColorGeometryInstanceAttribute.equals = function(left, right) {
return (left === right) ||
(defined(left) &&
defined(right) &&
left.value[0] === right.value[0] &&
left.value[1] === right.value[1] &&
left.value[2] === right.value[2] &&
left.value[3] === right.value[3]);
};
return ColorGeometryInstanceAttribute;
});
/*global define*/
define('Core/combine',[
'./defaultValue',
'./defined'
], function(
defaultValue,
defined) {
'use strict';
/**
* Merges two objects, copying their properties onto a new combined object. When two objects have the same
* property, the value of the property on the first object is used. If either object is undefined,
* it will be treated as an empty object.
*
* @example
* var object1 = {
* propOne : 1,
* propTwo : {
* value1 : 10
* }
* }
* var object2 = {
* propTwo : 2
* }
* var final = Cesium.combine(object1, object2);
*
* // final === {
* // propOne : 1,
* // propTwo : {
* // value1 : 10
* // }
* // }
*
* @param {Object} [object1] The first object to merge.
* @param {Object} [object2] The second object to merge.
* @param {Boolean} [deep=false] Perform a recursive merge.
* @returns {Object} The combined object containing all properties from both objects.
*
* @exports combine
*/
function combine(object1, object2, deep) {
deep = defaultValue(deep, false);
var result = {};
var object1Defined = defined(object1);
var object2Defined = defined(object2);
var property;
var object1Value;
var object2Value;
if (object1Defined) {
for (property in object1) {
if (object1.hasOwnProperty(property)) {
object1Value = object1[property];
if (object2Defined && deep && typeof object1Value === 'object' && object2.hasOwnProperty(property)) {
object2Value = object2[property];
if (typeof object2Value === 'object') {
result[property] = combine(object1Value, object2Value, deep);
} else {
result[property] = object1Value;
}
} else {
result[property] = object1Value;
}
}
}
}
if (object2Defined) {
for (property in object2) {
if (object2.hasOwnProperty(property) && !result.hasOwnProperty(property)) {
object2Value = object2[property];
result[property] = object2Value;
}
}
}
return result;
}
return combine;
});
/*global define*/
define('Core/CornerType',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Style options for corners.
*
* @demo The {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html&label=Geometries|Corridor Demo}
* demonstrates the three corner types, as used by {@link CorridorGraphics}.
*
* @exports CornerType
*/
var CornerType = {
/**
*
*
* Corner has a smooth edge.
* @type {Number}
* @constant
*/
ROUNDED : 0,
/**
*
*
* Corner point is the intersection of adjacent edges.
* @type {Number}
* @constant
*/
MITERED : 1,
/**
*
*
* Corner is clipped.
* @type {Number}
* @constant
*/
BEVELED : 2
};
return freezeObject(CornerType);
});
/*global define*/
define('Core/EllipsoidGeodesic',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Math'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
CesiumMath) {
'use strict';
function setConstants(ellipsoidGeodesic) {
var uSquared = ellipsoidGeodesic._uSquared;
var a = ellipsoidGeodesic._ellipsoid.maximumRadius;
var b = ellipsoidGeodesic._ellipsoid.minimumRadius;
var f = (a - b) / a;
var cosineHeading = Math.cos(ellipsoidGeodesic._startHeading);
var sineHeading = Math.sin(ellipsoidGeodesic._startHeading);
var tanU = (1 - f) * Math.tan(ellipsoidGeodesic._start.latitude);
var cosineU = 1.0 / Math.sqrt(1.0 + tanU * tanU);
var sineU = cosineU * tanU;
var sigma = Math.atan2(tanU, cosineHeading);
var sineAlpha = cosineU * sineHeading;
var sineSquaredAlpha = sineAlpha * sineAlpha;
var cosineSquaredAlpha = 1.0 - sineSquaredAlpha;
var cosineAlpha = Math.sqrt(cosineSquaredAlpha);
var u2Over4 = uSquared / 4.0;
var u4Over16 = u2Over4 * u2Over4;
var u6Over64 = u4Over16 * u2Over4;
var u8Over256 = u4Over16 * u4Over16;
var a0 = (1.0 + u2Over4 - 3.0 * u4Over16 / 4.0 + 5.0 * u6Over64 / 4.0 - 175.0 * u8Over256 / 64.0);
var a1 = (1.0 - u2Over4 + 15.0 * u4Over16 / 8.0 - 35.0 * u6Over64 / 8.0);
var a2 = (1.0 - 3.0 * u2Over4 + 35.0 * u4Over16 / 4.0);
var a3 = (1.0 - 5.0 * u2Over4);
var distanceRatio = a0 * sigma - a1 * Math.sin(2.0 * sigma) * u2Over4 / 2.0 - a2 * Math.sin(4.0 * sigma) * u4Over16 / 16.0 -
a3 * Math.sin(6.0 * sigma) * u6Over64 / 48.0 - Math.sin(8.0 * sigma) * 5.0 * u8Over256 / 512;
var constants = ellipsoidGeodesic._constants;
constants.a = a;
constants.b = b;
constants.f = f;
constants.cosineHeading = cosineHeading;
constants.sineHeading = sineHeading;
constants.tanU = tanU;
constants.cosineU = cosineU;
constants.sineU = sineU;
constants.sigma = sigma;
constants.sineAlpha = sineAlpha;
constants.sineSquaredAlpha = sineSquaredAlpha;
constants.cosineSquaredAlpha = cosineSquaredAlpha;
constants.cosineAlpha = cosineAlpha;
constants.u2Over4 = u2Over4;
constants.u4Over16 = u4Over16;
constants.u6Over64 = u6Over64;
constants.u8Over256 = u8Over256;
constants.a0 = a0;
constants.a1 = a1;
constants.a2 = a2;
constants.a3 = a3;
constants.distanceRatio = distanceRatio;
}
function computeC(f, cosineSquaredAlpha) {
return f * cosineSquaredAlpha * (4.0 + f * (4.0 - 3.0 * cosineSquaredAlpha)) / 16.0;
}
function computeDeltaLambda(f, sineAlpha, cosineSquaredAlpha, sigma, sineSigma, cosineSigma, cosineTwiceSigmaMidpoint) {
var C = computeC(f, cosineSquaredAlpha);
return (1.0 - C) * f * sineAlpha * (sigma + C * sineSigma * (cosineTwiceSigmaMidpoint +
C * cosineSigma * (2.0 * cosineTwiceSigmaMidpoint * cosineTwiceSigmaMidpoint - 1.0)));
}
function vincentyInverseFormula(ellipsoidGeodesic, major, minor, firstLongitude, firstLatitude, secondLongitude, secondLatitude) {
var eff = (major - minor) / major;
var l = secondLongitude - firstLongitude;
var u1 = Math.atan((1 - eff) * Math.tan(firstLatitude));
var u2 = Math.atan((1 - eff) * Math.tan(secondLatitude));
var cosineU1 = Math.cos(u1);
var sineU1 = Math.sin(u1);
var cosineU2 = Math.cos(u2);
var sineU2 = Math.sin(u2);
var cc = cosineU1 * cosineU2;
var cs = cosineU1 * sineU2;
var ss = sineU1 * sineU2;
var sc = sineU1 * cosineU2;
var lambda = l;
var lambdaDot = CesiumMath.TWO_PI;
var cosineLambda = Math.cos(lambda);
var sineLambda = Math.sin(lambda);
var sigma;
var cosineSigma;
var sineSigma;
var cosineSquaredAlpha;
var cosineTwiceSigmaMidpoint;
do {
cosineLambda = Math.cos(lambda);
sineLambda = Math.sin(lambda);
var temp = cs - sc * cosineLambda;
sineSigma = Math.sqrt(cosineU2 * cosineU2 * sineLambda * sineLambda + temp * temp);
cosineSigma = ss + cc * cosineLambda;
sigma = Math.atan2(sineSigma, cosineSigma);
var sineAlpha;
if (sineSigma === 0.0) {
sineAlpha = 0.0;
cosineSquaredAlpha = 1.0;
} else {
sineAlpha = cc * sineLambda / sineSigma;
cosineSquaredAlpha = 1.0 - sineAlpha * sineAlpha;
}
lambdaDot = lambda;
cosineTwiceSigmaMidpoint = cosineSigma - 2.0 * ss / cosineSquaredAlpha;
if (isNaN(cosineTwiceSigmaMidpoint)) {
cosineTwiceSigmaMidpoint = 0.0;
}
lambda = l + computeDeltaLambda(eff, sineAlpha, cosineSquaredAlpha,
sigma, sineSigma, cosineSigma, cosineTwiceSigmaMidpoint);
} while (Math.abs(lambda - lambdaDot) > CesiumMath.EPSILON12);
var uSquared = cosineSquaredAlpha * (major * major - minor * minor) / (minor * minor);
var A = 1.0 + uSquared * (4096.0 + uSquared * (uSquared * (320.0 - 175.0 * uSquared) - 768.0)) / 16384.0;
var B = uSquared * (256.0 + uSquared * (uSquared * (74.0 - 47.0 * uSquared) - 128.0)) / 1024.0;
var cosineSquaredTwiceSigmaMidpoint = cosineTwiceSigmaMidpoint * cosineTwiceSigmaMidpoint;
var deltaSigma = B * sineSigma * (cosineTwiceSigmaMidpoint + B * (cosineSigma *
(2.0 * cosineSquaredTwiceSigmaMidpoint - 1.0) - B * cosineTwiceSigmaMidpoint *
(4.0 * sineSigma * sineSigma - 3.0) * (4.0 * cosineSquaredTwiceSigmaMidpoint - 3.0) / 6.0) / 4.0);
var distance = minor * A * (sigma - deltaSigma);
var startHeading = Math.atan2(cosineU2 * sineLambda, cs - sc * cosineLambda);
var endHeading = Math.atan2(cosineU1 * sineLambda, cs * cosineLambda - sc);
ellipsoidGeodesic._distance = distance;
ellipsoidGeodesic._startHeading = startHeading;
ellipsoidGeodesic._endHeading = endHeading;
ellipsoidGeodesic._uSquared = uSquared;
}
function computeProperties(ellipsoidGeodesic, start, end, ellipsoid) {
var firstCartesian = Cartesian3.normalize(ellipsoid.cartographicToCartesian(start, scratchCart2), scratchCart1);
var lastCartesian = Cartesian3.normalize(ellipsoid.cartographicToCartesian(end, scratchCart2), scratchCart2);
if (Math.abs(Math.abs(Cartesian3.angleBetween(firstCartesian, lastCartesian)) - Math.PI) < 0.0125) {
throw new DeveloperError('geodesic position is not unique');
}
vincentyInverseFormula(ellipsoidGeodesic, ellipsoid.maximumRadius, ellipsoid.minimumRadius,
start.longitude, start.latitude, end.longitude, end.latitude);
ellipsoidGeodesic._start = Cartographic.clone(start, ellipsoidGeodesic._start);
ellipsoidGeodesic._end = Cartographic.clone(end, ellipsoidGeodesic._end);
ellipsoidGeodesic._start.height = 0;
ellipsoidGeodesic._end.height = 0;
setConstants(ellipsoidGeodesic);
}
var scratchCart1 = new Cartesian3();
var scratchCart2 = new Cartesian3();
/**
* Initializes a geodesic on the ellipsoid connecting the two provided planetodetic points.
*
* @alias EllipsoidGeodesic
* @constructor
*
* @param {Cartographic} [start] The initial planetodetic point on the path.
* @param {Cartographic} [end] The final planetodetic point on the path.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the geodesic lies.
*/
function EllipsoidGeodesic(start, end, ellipsoid) {
var e = defaultValue(ellipsoid, Ellipsoid.WGS84);
this._ellipsoid = e;
this._start = new Cartographic();
this._end = new Cartographic();
this._constants = {};
this._startHeading = undefined;
this._endHeading = undefined;
this._distance = undefined;
this._uSquared = undefined;
if (defined(start) && defined(end)) {
computeProperties(this, start, end, e);
}
}
defineProperties(EllipsoidGeodesic.prototype, {
/**
* Gets the ellipsoid.
* @memberof EllipsoidGeodesic.prototype
* @type {Ellipsoid}
* @readonly
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Gets the surface distance between the start and end point
* @memberof EllipsoidGeodesic.prototype
* @type {Number}
* @readonly
*/
surfaceDistance : {
get : function() {
if (!defined(this._distance)) {
throw new DeveloperError('set end positions before getting surfaceDistance');
}
return this._distance;
}
},
/**
* Gets the initial planetodetic point on the path.
* @memberof EllipsoidGeodesic.prototype
* @type {Cartographic}
* @readonly
*/
start : {
get : function() {
return this._start;
}
},
/**
* Gets the final planetodetic point on the path.
* @memberof EllipsoidGeodesic.prototype
* @type {Cartographic}
* @readonly
*/
end : {
get : function() {
return this._end;
}
},
/**
* Gets the heading at the initial point.
* @memberof EllipsoidGeodesic.prototype
* @type {Number}
* @readonly
*/
startHeading : {
get : function() {
if (!defined(this._distance)) {
throw new DeveloperError('set end positions before getting startHeading');
}
return this._startHeading;
}
},
/**
* Gets the heading at the final point.
* @memberof EllipsoidGeodesic.prototype
* @type {Number}
* @readonly
*/
endHeading : {
get : function() {
if (!defined(this._distance)) {
throw new DeveloperError('set end positions before getting endHeading');
}
return this._endHeading;
}
}
});
/**
* Sets the start and end points of the geodesic
*
* @param {Cartographic} start The initial planetodetic point on the path.
* @param {Cartographic} end The final planetodetic point on the path.
*/
EllipsoidGeodesic.prototype.setEndPoints = function(start, end) {
if (!defined(start)) {
throw new DeveloperError('start cartographic position is required');
}
if (!defined(end)) {
throw new DeveloperError('end cartgraphic position is required');
}
computeProperties(this, start, end, this._ellipsoid);
};
/**
* Provides the location of a point at the indicated portion along the geodesic.
*
* @param {Number} fraction The portion of the distance between the initial and final points.
* @returns {Cartographic} The location of the point along the geodesic.
*/
EllipsoidGeodesic.prototype.interpolateUsingFraction = function(fraction, result) {
return this.interpolateUsingSurfaceDistance(this._distance * fraction, result);
};
/**
* Provides the location of a point at the indicated distance along the geodesic.
*
* @param {Number} distance The distance from the inital point to the point of interest along the geodesic
* @returns {Cartographic} The location of the point along the geodesic.
*
* @exception {DeveloperError} start and end must be set before calling function interpolateUsingSurfaceDistance
*/
EllipsoidGeodesic.prototype.interpolateUsingSurfaceDistance = function(distance, result) {
if (!defined(this._distance)) {
throw new DeveloperError('start and end must be set before calling function interpolateUsingSurfaceDistance');
}
var constants = this._constants;
var s = constants.distanceRatio + distance / constants.b;
var cosine2S = Math.cos(2.0 * s);
var cosine4S = Math.cos(4.0 * s);
var cosine6S = Math.cos(6.0 * s);
var sine2S = Math.sin(2.0 * s);
var sine4S = Math.sin(4.0 * s);
var sine6S = Math.sin(6.0 * s);
var sine8S = Math.sin(8.0 * s);
var s2 = s * s;
var s3 = s * s2;
var u8Over256 = constants.u8Over256;
var u2Over4 = constants.u2Over4;
var u6Over64 = constants.u6Over64;
var u4Over16 = constants.u4Over16;
var sigma = 2.0 * s3 * u8Over256 * cosine2S / 3.0 +
s * (1.0 - u2Over4 + 7.0 * u4Over16 / 4.0 - 15.0 * u6Over64 / 4.0 + 579.0 * u8Over256 / 64.0 -
(u4Over16 - 15.0 * u6Over64 / 4.0 + 187.0 * u8Over256 / 16.0) * cosine2S -
(5.0 * u6Over64 / 4.0 - 115.0 * u8Over256 / 16.0) * cosine4S -
29.0 * u8Over256 * cosine6S / 16.0) +
(u2Over4 / 2.0 - u4Over16 + 71.0 * u6Over64 / 32.0 - 85.0 * u8Over256 / 16.0) * sine2S +
(5.0 * u4Over16 / 16.0 - 5.0 * u6Over64 / 4.0 + 383.0 * u8Over256 / 96.0) * sine4S -
s2 * ((u6Over64 - 11.0 * u8Over256 / 2.0) * sine2S + 5.0 * u8Over256 * sine4S / 2.0) +
(29.0 * u6Over64 / 96.0 - 29.0 * u8Over256 / 16.0) * sine6S +
539.0 * u8Over256 * sine8S / 1536.0;
var theta = Math.asin(Math.sin(sigma) * constants.cosineAlpha);
var latitude = Math.atan(constants.a / constants.b * Math.tan(theta));
// Redefine in terms of relative argument of latitude.
sigma = sigma - constants.sigma;
var cosineTwiceSigmaMidpoint = Math.cos(2.0 * constants.sigma + sigma);
var sineSigma = Math.sin(sigma);
var cosineSigma = Math.cos(sigma);
var cc = constants.cosineU * cosineSigma;
var ss = constants.sineU * sineSigma;
var lambda = Math.atan2(sineSigma * constants.sineHeading, cc - ss * constants.cosineHeading);
var l = lambda - computeDeltaLambda(constants.f, constants.sineAlpha, constants.cosineSquaredAlpha,
sigma, sineSigma, cosineSigma, cosineTwiceSigmaMidpoint);
if (defined(result)) {
result.longitude = this._start.longitude + l;
result.latitude = latitude;
result.height = 0.0;
return result;
}
return new Cartographic(this._start.longitude + l, latitude, 0.0);
};
return EllipsoidGeodesic;
});
/*global define*/
define('Core/PolylinePipeline',[
'./Cartesian3',
'./Cartographic',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./EllipsoidGeodesic',
'./IntersectionTests',
'./isArray',
'./Math',
'./Matrix4',
'./Plane'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
EllipsoidGeodesic,
IntersectionTests,
isArray,
CesiumMath,
Matrix4,
Plane) {
'use strict';
/**
* @private
*/
var PolylinePipeline = {};
PolylinePipeline.numberOfPoints = function(p0, p1, minDistance) {
var distance = Cartesian3.distance(p0, p1);
return Math.ceil(distance / minDistance);
};
var cartoScratch = new Cartographic();
PolylinePipeline.extractHeights = function(positions, ellipsoid) {
var length = positions.length;
var heights = new Array(length);
for (var i = 0; i < length; i++) {
var p = positions[i];
heights[i] = ellipsoid.cartesianToCartographic(p, cartoScratch).height;
}
return heights;
};
var wrapLongitudeInversMatrix = new Matrix4();
var wrapLongitudeOrigin = new Cartesian3();
var wrapLongitudeXZNormal = new Cartesian3();
var wrapLongitudeXZPlane = new Plane(Cartesian3.ZERO, 0.0);
var wrapLongitudeYZNormal = new Cartesian3();
var wrapLongitudeYZPlane = new Plane(Cartesian3.ZERO, 0.0);
var wrapLongitudeIntersection = new Cartesian3();
var wrapLongitudeOffset = new Cartesian3();
var subdivideHeightsScratchArray = [];
function subdivideHeights(numPoints, h0, h1) {
var heights = subdivideHeightsScratchArray;
heights.length = numPoints;
var i;
if (h0 === h1) {
for (i = 0; i < numPoints; i++) {
heights[i] = h0;
}
return heights;
}
var dHeight = h1 - h0;
var heightPerVertex = dHeight / numPoints;
for (i = 0; i < numPoints; i++) {
var h = h0 + i*heightPerVertex;
heights[i] = h;
}
return heights;
}
var carto1 = new Cartographic();
var carto2 = new Cartographic();
var cartesian = new Cartesian3();
var scaleFirst = new Cartesian3();
var scaleLast = new Cartesian3();
var ellipsoidGeodesic = new EllipsoidGeodesic();
//Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2.
//Result includes p1, but not include p2. This function is called for a sequence of line segments,
//and this prevents duplication of end point.
function generateCartesianArc(p0, p1, minDistance, ellipsoid, h0, h1, array, offset) {
var first = ellipsoid.scaleToGeodeticSurface(p0, scaleFirst);
var last = ellipsoid.scaleToGeodeticSurface(p1, scaleLast);
var numPoints = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
var start = ellipsoid.cartesianToCartographic(first, carto1);
var end = ellipsoid.cartesianToCartographic(last, carto2);
var heights = subdivideHeights(numPoints, h0, h1);
ellipsoidGeodesic.setEndPoints(start, end);
var surfaceDistanceBetweenPoints = ellipsoidGeodesic.surfaceDistance / numPoints;
var index = offset;
start.height = h0;
var cart = ellipsoid.cartographicToCartesian(start, cartesian);
Cartesian3.pack(cart, array, index);
index += 3;
for (var i = 1; i < numPoints; i++) {
var carto = ellipsoidGeodesic.interpolateUsingSurfaceDistance(i * surfaceDistanceBetweenPoints, carto2);
carto.height = heights[i];
cart = ellipsoid.cartographicToCartesian(carto, cartesian);
Cartesian3.pack(cart, array, index);
index += 3;
}
return index;
}
/**
* Breaks a {@link Polyline} into segments such that it does not cross the ±180 degree meridian of an ellipsoid.
*
* @param {Cartesian3[]} positions The polyline's Cartesian positions.
* @param {Matrix4} [modelMatrix=Matrix4.IDENTITY] The polyline's model matrix. Assumed to be an affine
* transformation matrix, where the upper left 3x3 elements are a rotation matrix, and
* the upper three elements in the fourth column are the translation. The bottom row is assumed to be [0, 0, 0, 1].
* The matrix is not verified to be in the proper form.
* @returns {Object} An object with a positions
property that is an array of positions and a
* segments
property.
*
*
* @example
* var polylines = new Cesium.PolylineCollection();
* var polyline = polylines.add(...);
* var positions = polyline.positions;
* var modelMatrix = polylines.modelMatrix;
* var segments = Cesium.PolylinePipeline.wrapLongitude(positions, modelMatrix);
*
* @see PolygonPipeline.wrapLongitude
* @see Polyline
* @see PolylineCollection
*/
PolylinePipeline.wrapLongitude = function(positions, modelMatrix) {
var cartesians = [];
var segments = [];
if (defined(positions) && positions.length > 0) {
modelMatrix = defaultValue(modelMatrix, Matrix4.IDENTITY);
var inverseModelMatrix = Matrix4.inverseTransformation(modelMatrix, wrapLongitudeInversMatrix);
var origin = Matrix4.multiplyByPoint(inverseModelMatrix, Cartesian3.ZERO, wrapLongitudeOrigin);
var xzNormal = Matrix4.multiplyByPointAsVector(inverseModelMatrix, Cartesian3.UNIT_Y, wrapLongitudeXZNormal);
var xzPlane = Plane.fromPointNormal(origin, xzNormal, wrapLongitudeXZPlane);
var yzNormal = Matrix4.multiplyByPointAsVector(inverseModelMatrix, Cartesian3.UNIT_X, wrapLongitudeYZNormal);
var yzPlane = Plane.fromPointNormal(origin, yzNormal, wrapLongitudeYZPlane);
var count = 1;
cartesians.push(Cartesian3.clone(positions[0]));
var prev = cartesians[0];
var length = positions.length;
for (var i = 1; i < length; ++i) {
var cur = positions[i];
// intersects the IDL if either endpoint is on the negative side of the yz-plane
if (Plane.getPointDistance(yzPlane, prev) < 0.0 || Plane.getPointDistance(yzPlane, cur) < 0.0) {
// and intersects the xz-plane
var intersection = IntersectionTests.lineSegmentPlane(prev, cur, xzPlane, wrapLongitudeIntersection);
if (defined(intersection)) {
// move point on the xz-plane slightly away from the plane
var offset = Cartesian3.multiplyByScalar(xzNormal, 5.0e-9, wrapLongitudeOffset);
if (Plane.getPointDistance(xzPlane, prev) < 0.0) {
Cartesian3.negate(offset, offset);
}
cartesians.push(Cartesian3.add(intersection, offset, new Cartesian3()));
segments.push(count + 1);
Cartesian3.negate(offset, offset);
cartesians.push(Cartesian3.add(intersection, offset, new Cartesian3()));
count = 1;
}
}
cartesians.push(Cartesian3.clone(positions[i]));
count++;
prev = cur;
}
segments.push(count);
}
return {
positions : cartesians,
lengths : segments
};
};
/**
* Subdivides polyline and raises all points to the specified height. Returns an array of numbers to represent the positions.
* @param {Cartesian3[]} positions The array of type {Cartesian3} representing positions.
* @param {Number|Number[]} [height=0.0] A number or array of numbers representing the heights of each position.
* @param {Number} [granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
* @returns {Number[]} A new array of positions of type {Number} that have been subdivided and raised to the surface of the ellipsoid.
*
* @example
* var positions = Cesium.Cartesian3.fromDegreesArray([
* -105.0, 40.0,
* -100.0, 38.0,
* -105.0, 35.0,
* -100.0, 32.0
* ]);
* var surfacePositions = Cesium.PolylinePipeline.generateArc({
* positons: positions
* });
*/
PolylinePipeline.generateArc = function(options) {
if (!defined(options)) {
options = {};
}
var positions = options.positions;
if (!defined(positions)) {
throw new DeveloperError('options.positions is required.');
}
var length = positions.length;
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var height = defaultValue(options.height, 0);
var hasHeightArray = isArray(height);
if (length < 1) {
return [];
} else if (length === 1) {
var p = ellipsoid.scaleToGeodeticSurface(positions[0], scaleFirst);
height = hasHeightArray ? height[0] : height;
if (height !== 0) {
var n = ellipsoid.geodeticSurfaceNormal(p, cartesian);
Cartesian3.multiplyByScalar(n, height, n);
Cartesian3.add(p, n, p);
}
return [p.x, p.y, p.z];
}
var minDistance = options.minDistance;
if (!defined(minDistance)) {
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
}
var numPoints = 0;
var i;
for (i = 0; i < length -1; i++) {
numPoints += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance);
}
var arrayLength = (numPoints + 1) * 3;
var newPositions = new Array(arrayLength);
var offset = 0;
for (i = 0; i < length - 1; i++) {
var p0 = positions[i];
var p1 = positions[i + 1];
var h0 = hasHeightArray ? height[i] : height;
var h1 = hasHeightArray ? height[i + 1] : height;
offset = generateCartesianArc(p0, p1, minDistance, ellipsoid, h0, h1, newPositions, offset);
}
subdivideHeightsScratchArray.length = 0;
var lastPoint = positions[length - 1];
var carto = ellipsoid.cartesianToCartographic(lastPoint, carto1);
carto.height = hasHeightArray ? height[length - 1] : height;
var cart = ellipsoid.cartographicToCartesian(carto, cartesian);
Cartesian3.pack(cart, newPositions, arrayLength - 3);
return newPositions;
};
/**
* Subdivides polyline and raises all points to the specified height. Returns an array of new {Cartesian3} positions.
* @param {Cartesian3[]} positions The array of type {Cartesian3} representing positions.
* @param {Number|Number[]} [height=0.0] A number or array of numbers representing the heights of each position.
* @param {Number} [granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
* @returns {Cartesian3[]} A new array of cartesian3 positions that have been subdivided and raised to the surface of the ellipsoid.
*
* @example
* var positions = Cesium.Cartesian3.fromDegreesArray([
* -105.0, 40.0,
* -100.0, 38.0,
* -105.0, 35.0,
* -100.0, 32.0
* ]);
* var surfacePositions = Cesium.PolylinePipeline.generateCartesianArc({
* positons: positions
* });
*/
PolylinePipeline.generateCartesianArc = function(options) {
var numberArray = PolylinePipeline.generateArc(options);
var size = numberArray.length/3;
var newPositions = new Array(size);
for (var i = 0; i < size; i++) {
newPositions[i] = Cartesian3.unpack(numberArray, i*3);
}
return newPositions;
};
return PolylinePipeline;
});
/*global define*/
define('Core/PolylineVolumeGeometryLibrary',[
'./Cartesian2',
'./Cartesian3',
'./Cartesian4',
'./Cartographic',
'./CornerType',
'./EllipsoidTangentPlane',
'./Math',
'./Matrix3',
'./Matrix4',
'./PolylinePipeline',
'./Quaternion',
'./Transforms'
], function(
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
CornerType,
EllipsoidTangentPlane,
CesiumMath,
Matrix3,
Matrix4,
PolylinePipeline,
Quaternion,
Transforms) {
'use strict';
var scratch2Array = [new Cartesian3(), new Cartesian3()];
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4 = new Cartesian3();
var scratchCartesian5 = new Cartesian3();
var scratchCartesian6 = new Cartesian3();
var scratchCartesian7 = new Cartesian3();
var scratchCartesian8 = new Cartesian3();
var scratchCartesian9 = new Cartesian3();
var scratch1 = new Cartesian3();
var scratch2 = new Cartesian3();
/**
* @private
*/
var PolylineVolumeGeometryLibrary = {};
var cartographic = new Cartographic();
function scaleToSurface(positions, ellipsoid) {
var heights = new Array(positions.length);
for (var i = 0; i < positions.length; i++) {
var pos = positions[i];
cartographic = ellipsoid.cartesianToCartographic(pos, cartographic);
heights[i] = cartographic.height;
positions[i] = ellipsoid.scaleToGeodeticSurface(pos, pos);
}
return heights;
}
function subdivideHeights(points, h0, h1, granularity) {
var p0 = points[0];
var p1 = points[1];
var angleBetween = Cartesian3.angleBetween(p0, p1);
var numPoints = Math.ceil(angleBetween / granularity);
var heights = new Array(numPoints);
var i;
if (h0 === h1) {
for (i = 0; i < numPoints; i++) {
heights[i] = h0;
}
heights.push(h1);
return heights;
}
var dHeight = h1 - h0;
var heightPerVertex = dHeight / (numPoints);
for (i = 1; i < numPoints; i++) {
var h = h0 + i * heightPerVertex;
heights[i] = h;
}
heights[0] = h0;
heights.push(h1);
return heights;
}
function computeRotationAngle(start, end, position, ellipsoid) {
var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid);
var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch);
var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch);
var angle = Cartesian2.angleBetween(next, prev);
return (prev.x * next.y - prev.y * next.x >= 0.0) ? -angle : angle;
}
var negativeX = new Cartesian3(-1, 0, 0);
var transform = new Matrix4();
var translation = new Matrix4();
var rotationZ = new Matrix3();
var scaleMatrix = Matrix3.IDENTITY.clone();
var westScratch = new Cartesian3();
var finalPosScratch = new Cartesian4();
var heightCartesian = new Cartesian3();
function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat) {
var west = westScratch;
var finalPosition = finalPosScratch;
transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform);
west = Matrix4.multiplyByPointAsVector(transform, negativeX, west);
west = Cartesian3.normalize(west, west);
var angle = computeRotationAngle(west, left, center, ellipsoid);
rotationZ = Matrix3.fromRotationZ(angle, rotationZ);
heightCartesian.z = height;
transform = Matrix4.multiplyTransformation(transform, Matrix4.fromRotationTranslation(rotationZ, heightCartesian, translation), transform);
var scale = scaleMatrix;
scale[0] = xScalar;
for (var j = 0; j < repeat; j++) {
for (var i = 0; i < shape.length; i += 3) {
finalPosition = Cartesian3.fromArray(shape, i, finalPosition);
finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition);
finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition);
finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z);
}
}
return finalPositions;
}
var centerScratch = new Cartesian3();
function addPositions(centers, left, shape, finalPositions, ellipsoid, heights, xScalar) {
for (var i = 0; i < centers.length; i += 3) {
var center = Cartesian3.fromArray(centers, i, centerScratch);
finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, heights[i / 3], xScalar, 1);
}
return finalPositions;
}
function convertShapeTo3DDuplicate(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points
var length = shape2D.length;
var shape = new Array(length * 6);
var index = 0;
var xOffset = boundingRectangle.x + boundingRectangle.width / 2;
var yOffset = boundingRectangle.y + boundingRectangle.height / 2;
var point = shape2D[0];
shape[index++] = point.x - xOffset;
shape[index++] = 0.0;
shape[index++] = point.y - yOffset;
for (var i = 1; i < length; i++) {
point = shape2D[i];
var x = point.x - xOffset;
var z = point.y - yOffset;
shape[index++] = x;
shape[index++] = 0.0;
shape[index++] = z;
shape[index++] = x;
shape[index++] = 0.0;
shape[index++] = z;
}
point = shape2D[0];
shape[index++] = point.x - xOffset;
shape[index++] = 0.0;
shape[index++] = point.y - yOffset;
return shape;
}
function convertShapeTo3D(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0)
var length = shape2D.length;
var shape = new Array(length * 3);
var index = 0;
var xOffset = boundingRectangle.x + boundingRectangle.width / 2;
var yOffset = boundingRectangle.y + boundingRectangle.height / 2;
for (var i = 0; i < length; i++) {
shape[index++] = shape2D[i].x - xOffset;
shape[index++] = 0;
shape[index++] = shape2D[i].y - yOffset;
}
return shape;
}
var quaterion = new Quaternion();
var startPointScratch = new Cartesian3();
var rotMatrix = new Matrix3();
function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height, duplicatePoints) {
var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2));
var granularity = (cornerType === CornerType.BEVELED) ? 0 : Math.ceil(angle / CesiumMath.toRadians(5));
var m;
if (leftIsOutside) {
m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion), rotMatrix);
} else {
m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix);
}
var left;
var surfacePoint;
startPoint = Cartesian3.clone(startPoint, startPointScratch);
if (granularity > 0) {
var repeat = duplicatePoints ? 2 : 1;
for (var i = 0; i < granularity; i++) {
startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint);
left = Cartesian3.subtract(startPoint, pivot, scratch1);
left = Cartesian3.normalize(left, left);
if (!leftIsOutside) {
left = Cartesian3.negate(left, left);
}
surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2);
finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, repeat);
}
} else {
left = Cartesian3.subtract(startPoint, pivot, scratch1);
left = Cartesian3.normalize(left, left);
if (!leftIsOutside) {
left = Cartesian3.negate(left, left);
}
surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2);
finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1);
endPoint = Cartesian3.clone(endPoint, startPointScratch);
left = Cartesian3.subtract(endPoint, pivot, scratch1);
left = Cartesian3.normalize(left, left);
if (!leftIsOutside) {
left = Cartesian3.negate(left, left);
}
surfacePoint = ellipsoid.scaleToGeodeticSurface(endPoint, scratch2);
finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1);
}
return finalPositions;
}
PolylineVolumeGeometryLibrary.removeDuplicatesFromShape = function(shapePositions) {
var length = shapePositions.length;
var cleanedPositions = [];
for (var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) {
var v0 = shapePositions[i0];
var v1 = shapePositions[i1];
if (!Cartesian2.equals(v0, v1)) {
cleanedPositions.push(v1); // Shallow copy!
}
}
return cleanedPositions;
};
var nextScratch = new Cartesian3();
var prevScratch = new Cartesian3();
PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) {
var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid);
var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch);
var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch);
return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0;
};
var scratchForwardProjection = new Cartesian3();
var scratchBackwardProjection = new Cartesian3();
PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) {
var ellipsoid = geometry._ellipsoid;
var heights = scaleToSurface(positions, ellipsoid);
var granularity = geometry._granularity;
var cornerType = geometry._cornerType;
var shapeForSides = duplicatePoints ? convertShapeTo3DDuplicate(shape2D, boundingRectangle) : convertShapeTo3D(shape2D, boundingRectangle);
var shapeForEnds = duplicatePoints ? convertShapeTo3D(shape2D, boundingRectangle) : undefined;
var heightOffset = boundingRectangle.height / 2;
var width = boundingRectangle.width / 2;
var length = positions.length;
var finalPositions = [];
var ends = duplicatePoints ? [] : undefined;
var forward = scratchCartesian1;
var backward = scratchCartesian2;
var cornerDirection = scratchCartesian3;
var surfaceNormal = scratchCartesian4;
var pivot = scratchCartesian5;
var start = scratchCartesian6;
var end = scratchCartesian7;
var left = scratchCartesian8;
var previousPosition = scratchCartesian9;
var position = positions[0];
var nextPosition = positions[1];
surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal);
forward = Cartesian3.subtract(nextPosition, position, forward);
forward = Cartesian3.normalize(forward, forward);
left = Cartesian3.cross(surfaceNormal, forward, left);
left = Cartesian3.normalize(left, left);
var h0 = heights[0];
var h1 = heights[1];
if (duplicatePoints) {
ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h0 + heightOffset, 1, 1);
}
previousPosition = Cartesian3.clone(position, previousPosition);
position = nextPosition;
backward = Cartesian3.negate(forward, backward);
var subdividedHeights;
var subdividedPositions;
for (var i = 1; i < length - 1; i++) {
var repeat = duplicatePoints ? 2 : 1;
nextPosition = positions[i + 1];
forward = Cartesian3.subtract(nextPosition, position, forward);
forward = Cartesian3.normalize(forward, forward);
cornerDirection = Cartesian3.add(forward, backward, cornerDirection);
cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection);
surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal);
var forwardProjection = Cartesian3.multiplyByScalar(surfaceNormal, Cartesian3.dot(forward, surfaceNormal), scratchForwardProjection);
Cartesian3.subtract(forward, forwardProjection, forwardProjection);
Cartesian3.normalize(forwardProjection, forwardProjection);
var backwardProjection = Cartesian3.multiplyByScalar(surfaceNormal, Cartesian3.dot(backward, surfaceNormal), scratchBackwardProjection);
Cartesian3.subtract(backward, backwardProjection, backwardProjection);
Cartesian3.normalize(backwardProjection, backwardProjection);
var doCorner = !CesiumMath.equalsEpsilon(Math.abs(Cartesian3.dot(forwardProjection, backwardProjection)), 1.0, CesiumMath.EPSILON7);
if (doCorner) {
cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection);
cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection);
cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection);
var scalar = 1 / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1))));
var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid);
if (leftIsOutside) {
pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot);
start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start);
scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]);
scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]);
subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity);
subdividedPositions = PolylinePipeline.generateArc({
positions: scratch2Array,
granularity: granularity,
ellipsoid: ellipsoid
});
finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1);
left = Cartesian3.cross(surfaceNormal, forward, left);
left = Cartesian3.normalize(left, left);
end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end);
if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints);
} else {
cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection);
finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat);
}
previousPosition = Cartesian3.clone(end, previousPosition);
} else {
pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot);
start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start);
scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]);
scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]);
subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity);
subdividedPositions = PolylinePipeline.generateArc({
positions: scratch2Array,
granularity: granularity,
ellipsoid: ellipsoid
});
finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1);
left = Cartesian3.cross(surfaceNormal, forward, left);
left = Cartesian3.normalize(left, left);
end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end);
if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints);
} else {
finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat);
}
previousPosition = Cartesian3.clone(end, previousPosition);
}
backward = Cartesian3.negate(forward, backward);
} else {
finalPositions = addPosition(previousPosition, left, shapeForSides, finalPositions, ellipsoid, h0 + heightOffset, 1, 1);
previousPosition = position;
}
h0 = h1;
h1 = heights[i + 1];
position = nextPosition;
}
scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]);
scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]);
subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity);
subdividedPositions = PolylinePipeline.generateArc({
positions: scratch2Array,
granularity: granularity,
ellipsoid: ellipsoid
});
finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1);
if (duplicatePoints) {
ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h1 + heightOffset, 1, 1);
}
length = finalPositions.length;
var posLength = duplicatePoints ? length + ends.length : length;
var combinedPositions = new Float64Array(posLength);
combinedPositions.set(finalPositions);
if (duplicatePoints) {
combinedPositions.set(ends, length);
}
return combinedPositions;
};
return PolylineVolumeGeometryLibrary;
});
/*global define*/
define('Core/CorridorGeometryLibrary',[
'./Cartesian3',
'./CornerType',
'./defined',
'./Math',
'./Matrix3',
'./PolylinePipeline',
'./PolylineVolumeGeometryLibrary',
'./Quaternion'
], function(
Cartesian3,
CornerType,
defined,
CesiumMath,
Matrix3,
PolylinePipeline,
PolylineVolumeGeometryLibrary,
Quaternion) {
'use strict';
/**
* @private
*/
var CorridorGeometryLibrary = {};
var scratch1 = new Cartesian3();
var scratch2 = new Cartesian3();
var scratch3 = new Cartesian3();
var scratch4 = new Cartesian3();
var scaleArray2 = [new Cartesian3(), new Cartesian3()];
var cartesian1 = new Cartesian3();
var cartesian2 = new Cartesian3();
var cartesian3 = new Cartesian3();
var cartesian4 = new Cartesian3();
var cartesian5 = new Cartesian3();
var cartesian6 = new Cartesian3();
var cartesian7 = new Cartesian3();
var cartesian8 = new Cartesian3();
var cartesian9 = new Cartesian3();
var cartesian10 = new Cartesian3();
var quaterion = new Quaternion();
var rotMatrix = new Matrix3();
function computeRoundCorner(cornerPoint, startPoint, endPoint, cornerType, leftIsOutside) {
var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, cornerPoint, scratch1), Cartesian3.subtract(endPoint, cornerPoint, scratch2));
var granularity = (cornerType === CornerType.BEVELED) ? 1 : Math.ceil(angle / CesiumMath.toRadians(5)) + 1;
var size = granularity * 3;
var array = new Array(size);
array[size - 3] = endPoint.x;
array[size - 2] = endPoint.y;
array[size - 1] = endPoint.z;
var m;
if (leftIsOutside) {
m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(cornerPoint, scratch1), angle / granularity, quaterion), rotMatrix);
} else {
m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(cornerPoint, angle / granularity, quaterion), rotMatrix);
}
var index = 0;
startPoint = Cartesian3.clone(startPoint, scratch1);
for (var i = 0; i < granularity; i++) {
startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint);
array[index++] = startPoint.x;
array[index++] = startPoint.y;
array[index++] = startPoint.z;
}
return array;
}
function addEndCaps(calculatedPositions) {
var cornerPoint = cartesian1;
var startPoint = cartesian2;
var endPoint = cartesian3;
var leftEdge = calculatedPositions[1];
startPoint = Cartesian3.fromArray(calculatedPositions[1], leftEdge.length - 3, startPoint);
endPoint = Cartesian3.fromArray(calculatedPositions[0], 0, endPoint);
cornerPoint = Cartesian3.multiplyByScalar(Cartesian3.add(startPoint, endPoint, cornerPoint), 0.5, cornerPoint);
var firstEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false);
var length = calculatedPositions.length - 1;
var rightEdge = calculatedPositions[length - 1];
leftEdge = calculatedPositions[length];
startPoint = Cartesian3.fromArray(rightEdge, rightEdge.length - 3, startPoint);
endPoint = Cartesian3.fromArray(leftEdge, 0, endPoint);
cornerPoint = Cartesian3.multiplyByScalar(Cartesian3.add(startPoint, endPoint, cornerPoint), 0.5, cornerPoint);
var lastEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false);
return [firstEndCap, lastEndCap];
}
function computeMiteredCorner(position, leftCornerDirection, lastPoint, leftIsOutside) {
var cornerPoint = scratch1;
if (leftIsOutside) {
cornerPoint = Cartesian3.add(position, leftCornerDirection, cornerPoint);
} else {
leftCornerDirection = Cartesian3.negate(leftCornerDirection, leftCornerDirection);
cornerPoint = Cartesian3.add(position, leftCornerDirection, cornerPoint);
}
return [cornerPoint.x, cornerPoint.y, cornerPoint.z, lastPoint.x, lastPoint.y, lastPoint.z];
}
function addShiftedPositions(positions, left, scalar, calculatedPositions) {
var rightPositions = new Array(positions.length);
var leftPositions = new Array(positions.length);
var scaledLeft = Cartesian3.multiplyByScalar(left, scalar, scratch1);
var scaledRight = Cartesian3.negate(scaledLeft, scratch2);
var rightIndex = 0;
var leftIndex = positions.length - 1;
for (var i = 0; i < positions.length; i += 3) {
var pos = Cartesian3.fromArray(positions, i, scratch3);
var rightPos = Cartesian3.add(pos, scaledRight, scratch4);
rightPositions[rightIndex++] = rightPos.x;
rightPositions[rightIndex++] = rightPos.y;
rightPositions[rightIndex++] = rightPos.z;
var leftPos = Cartesian3.add(pos, scaledLeft, scratch4);
leftPositions[leftIndex--] = leftPos.z;
leftPositions[leftIndex--] = leftPos.y;
leftPositions[leftIndex--] = leftPos.x;
}
calculatedPositions.push(rightPositions, leftPositions);
return calculatedPositions;
}
/**
* @private
*/
CorridorGeometryLibrary.addAttribute = function(attribute, value, front, back) {
var x = value.x;
var y = value.y;
var z = value.z;
if (defined(front)) {
attribute[front] = x;
attribute[front + 1] = y;
attribute[front + 2] = z;
}
if (defined(back)) {
attribute[back] = z;
attribute[back - 1] = y;
attribute[back - 2] = x;
}
};
function scaleToSurface(positions, ellipsoid) {
for (var i = 0; i < positions.length; i++) {
positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]);
}
return positions;
}
var scratchForwardProjection = new Cartesian3();
var scratchBackwardProjection = new Cartesian3();
/**
* @private
*/
CorridorGeometryLibrary.computePositions = function(params) {
var granularity = params.granularity;
var positions = params.positions;
var ellipsoid = params.ellipsoid;
positions = scaleToSurface(positions, ellipsoid);
var width = params.width / 2;
var cornerType = params.cornerType;
var saveAttributes = params.saveAttributes;
var normal = cartesian1;
var forward = cartesian2;
var backward = cartesian3;
var left = cartesian4;
var cornerDirection = cartesian5;
var startPoint = cartesian6;
var previousPos = cartesian7;
var rightPos = cartesian8;
var leftPos = cartesian9;
var center = cartesian10;
var calculatedPositions = [];
var calculatedLefts = (saveAttributes) ? [] : undefined;
var calculatedNormals = (saveAttributes) ? [] : undefined;
var position = positions[0]; //add first point
var nextPosition = positions[1];
forward = Cartesian3.normalize(Cartesian3.subtract(nextPosition, position, forward), forward);
normal = ellipsoid.geodeticSurfaceNormal(position, normal);
left = Cartesian3.normalize(Cartesian3.cross(normal, forward, left), left);
if (saveAttributes) {
calculatedLefts.push(left.x, left.y, left.z);
calculatedNormals.push(normal.x, normal.y, normal.z);
}
previousPos = Cartesian3.clone(position, previousPos);
position = nextPosition;
backward = Cartesian3.negate(forward, backward);
var subdividedPositions;
var corners = [];
var i;
var length = positions.length;
for (i = 1; i < length - 1; i++) { // add middle points and corners
normal = ellipsoid.geodeticSurfaceNormal(position, normal);
nextPosition = positions[i + 1];
forward = Cartesian3.normalize(Cartesian3.subtract(nextPosition, position, forward), forward);
cornerDirection = Cartesian3.normalize(Cartesian3.add(forward, backward, cornerDirection), cornerDirection);
var forwardProjection = Cartesian3.multiplyByScalar(normal, Cartesian3.dot(forward, normal), scratchForwardProjection);
Cartesian3.subtract(forward, forwardProjection, forwardProjection);
Cartesian3.normalize(forwardProjection, forwardProjection);
var backwardProjection = Cartesian3.multiplyByScalar(normal, Cartesian3.dot(backward, normal), scratchBackwardProjection);
Cartesian3.subtract(backward, backwardProjection, backwardProjection);
Cartesian3.normalize(backwardProjection, backwardProjection);
var doCorner = !CesiumMath.equalsEpsilon(Math.abs(Cartesian3.dot(forwardProjection, backwardProjection)), 1.0, CesiumMath.EPSILON7);
if (doCorner) {
cornerDirection = Cartesian3.cross(cornerDirection, normal, cornerDirection);
cornerDirection = Cartesian3.cross(normal, cornerDirection, cornerDirection);
cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection);
var scalar = width / Math.max(0.25, Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)));
var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid);
cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection);
if (leftIsOutside) {
rightPos = Cartesian3.add(position, cornerDirection, rightPos);
center = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width, center), center);
leftPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width * 2, leftPos), leftPos);
scaleArray2[0] = Cartesian3.clone(previousPos, scaleArray2[0]);
scaleArray2[1] = Cartesian3.clone(center, scaleArray2[1]);
subdividedPositions = PolylinePipeline.generateArc({
positions: scaleArray2,
granularity: granularity,
ellipsoid: ellipsoid
});
calculatedPositions = addShiftedPositions(subdividedPositions, left, width, calculatedPositions);
if (saveAttributes) {
calculatedLefts.push(left.x, left.y, left.z);
calculatedNormals.push(normal.x, normal.y, normal.z);
}
startPoint = Cartesian3.clone(leftPos, startPoint);
left = Cartesian3.normalize(Cartesian3.cross(normal, forward, left), left);
leftPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width * 2, leftPos), leftPos);
previousPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width, previousPos), previousPos);
if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
corners.push({
leftPositions : computeRoundCorner(rightPos, startPoint, leftPos, cornerType, leftIsOutside)
});
} else {
corners.push({
leftPositions : computeMiteredCorner(position, Cartesian3.negate(cornerDirection, cornerDirection), leftPos, leftIsOutside)
});
}
} else {
leftPos = Cartesian3.add(position, cornerDirection, leftPos);
center = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width, center), center), center);
rightPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width * 2, rightPos), rightPos), rightPos);
scaleArray2[0] = Cartesian3.clone(previousPos, scaleArray2[0]);
scaleArray2[1] = Cartesian3.clone(center, scaleArray2[1]);
subdividedPositions = PolylinePipeline.generateArc({
positions: scaleArray2,
granularity: granularity,
ellipsoid: ellipsoid
});
calculatedPositions = addShiftedPositions(subdividedPositions, left, width, calculatedPositions);
if (saveAttributes) {
calculatedLefts.push(left.x, left.y, left.z);
calculatedNormals.push(normal.x, normal.y, normal.z);
}
startPoint = Cartesian3.clone(rightPos, startPoint);
left = Cartesian3.normalize(Cartesian3.cross(normal, forward, left), left);
rightPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width * 2, rightPos), rightPos), rightPos);
previousPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width, previousPos), previousPos), previousPos);
if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
corners.push({
rightPositions : computeRoundCorner(leftPos, startPoint, rightPos, cornerType, leftIsOutside)
});
} else {
corners.push({
rightPositions : computeMiteredCorner(position, cornerDirection, rightPos, leftIsOutside)
});
}
}
backward = Cartesian3.negate(forward, backward);
}
position = nextPosition;
}
normal = ellipsoid.geodeticSurfaceNormal(position, normal);
scaleArray2[0] = Cartesian3.clone(previousPos, scaleArray2[0]);
scaleArray2[1] = Cartesian3.clone(position, scaleArray2[1]);
subdividedPositions = PolylinePipeline.generateArc({
positions: scaleArray2,
granularity: granularity,
ellipsoid: ellipsoid
});
calculatedPositions = addShiftedPositions(subdividedPositions, left, width, calculatedPositions);
if (saveAttributes) {
calculatedLefts.push(left.x, left.y, left.z);
calculatedNormals.push(normal.x, normal.y, normal.z);
}
var endPositions;
if (cornerType === CornerType.ROUNDED) {
endPositions = addEndCaps(calculatedPositions);
}
return {
positions : calculatedPositions,
corners : corners,
lefts : calculatedLefts,
normals : calculatedNormals,
endPositions : endPositions
};
};
return CorridorGeometryLibrary;
});
/*global define*/
define('ThirdParty/earcut-2.1.1',[], function() {
'use strict';
function earcut(data, holeIndices, dim) {
dim = dim || 2;
var hasHoles = holeIndices && holeIndices.length,
outerLen = hasHoles ? holeIndices[0] * dim : data.length,
outerNode = linkedList(data, 0, outerLen, dim, true),
triangles = [];
if (!outerNode) return triangles;
var minX, minY, maxX, maxY, x, y, size;
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if (data.length > 80 * dim) {
minX = maxX = data[0];
minY = maxY = data[1];
for (var i = dim; i < outerLen; i += dim) {
x = data[i];
y = data[i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
// minX, minY and size are later used to transform coords into integers for z-order calculation
size = Math.max(maxX - minX, maxY - minY);
}
earcutLinked(outerNode, triangles, dim, minX, minY, size);
return triangles;
}
// create a circular doubly linked list from polygon points in the specified winding order
function linkedList(data, start, end, dim, clockwise) {
var i, last;
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
} else {
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
}
if (last && equals(last, last.next)) {
removeNode(last);
last = last.next;
}
return last;
}
// eliminate colinear or duplicate points
function filterPoints(start, end) {
if (!start) return start;
if (!end) end = start;
var p = start,
again;
do {
again = false;
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
removeNode(p);
p = end = p.prev;
if (p === p.next) return null;
again = true;
} else {
p = p.next;
}
} while (again || p !== end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && size) indexCurve(ear, minX, minY, size);
var stop = ear,
prev, next;
// iterate through ears, slicing them one by one
while (ear.prev !== ear.next) {
prev = ear.prev;
next = ear.next;
if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) {
// cut off the triangle
triangles.push(prev.i / dim);
triangles.push(ear.i / dim);
triangles.push(next.i / dim);
removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear === stop) {
// try filtering points and slicing again
if (!pass) {
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1);
// if this didn't work, try curing all small self-intersections locally
} else if (pass === 1) {
ear = cureLocalIntersections(ear, triangles, dim);
earcutLinked(ear, triangles, dim, minX, minY, size, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
splitEarcut(ear, triangles, dim, minX, minY, size);
}
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
function isEar(ear) {
var a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
var p = ear.next.next;
while (p !== ear.prev) {
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.next;
}
return true;
}
function isEarHashed(ear, minX, minY, size) {
var a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
// z-order range for the current triangle bbox;
var minZ = zOrder(minTX, minTY, minX, minY, size),
maxZ = zOrder(maxTX, maxTY, minX, minY, size);
// first look for points inside the triangle in increasing z-order
var p = ear.nextZ;
while (p && p.z <= maxZ) {
if (p !== ear.prev && p !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.nextZ;
}
// then look for points in decreasing z-order
p = ear.prevZ;
while (p && p.z >= minZ) {
if (p !== ear.prev && p !== ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections(start, triangles, dim) {
var p = start;
do {
var a = p.prev,
b = p.next.next;
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
triangles.push(a.i / dim);
triangles.push(p.i / dim);
triangles.push(b.i / dim);
// remove two nodes involved
removeNode(p);
removeNode(p.next);
p = start = b;
}
p = p.next;
} while (p !== start);
return p;
}
// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, size) {
// look for a valid diagonal that divides the polygon into two
var a = start;
do {
var b = a.next.next;
while (b !== a.prev) {
if (a.i !== b.i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
var c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a.next);
c = filterPoints(c, c.next);
// run earcut on each half
earcutLinked(a, triangles, dim, minX, minY, size);
earcutLinked(c, triangles, dim, minX, minY, size);
return;
}
b = b.next;
}
a = a.next;
} while (a !== start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
var queue = [],
i, len, start, end, list;
for (i = 0, len = holeIndices.length; i < len; i++) {
start = holeIndices[i] * dim;
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
list = linkedList(data, start, end, dim, false);
if (list === list.next) list.steiner = true;
queue.push(getLeftmost(list));
}
queue.sort(compareX);
// process holes from left to right
for (i = 0; i < queue.length; i++) {
eliminateHole(queue[i], outerNode);
outerNode = filterPoints(outerNode, outerNode.next);
}
return outerNode;
}
function compareX(a, b) {
return a.x - b.x;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole(hole, outerNode) {
outerNode = findHoleBridge(hole, outerNode);
if (outerNode) {
var b = splitPolygon(outerNode, hole);
filterPoints(b, b.next);
}
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge(hole, outerNode) {
var p = outerNode,
hx = hole.x,
hy = hole.y,
qx = -Infinity,
m;
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
do {
if (hy <= p.y && hy >= p.next.y) {
var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
if (x <= hx && x > qx) {
qx = x;
if (x === hx) {
if (hy === p.y) return p;
if (hy === p.next.y) return p.next;
}
m = p.x < p.next.x ? p : p.next;
}
}
p = p.next;
} while (p !== outerNode);
if (!m) return null;
if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint
// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the point of the minimum angle with the ray as connection point
var stop = m,
mx = m.x,
my = m.y,
tanMin = Infinity,
tan;
p = m.next;
while (p !== stop) {
if (hx >= p.x && p.x >= mx &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
m = p;
tanMin = tan;
}
}
p = p.next;
}
return m;
}
// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, size) {
var p = start;
do {
if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size);
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
} while (p !== start);
p.prevZ.nextZ = null;
p.prevZ = null;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked(list) {
var i, p, q, e, tail, numMerges, pSize, qSize,
inSize = 1;
do {
p = list;
list = null;
tail = null;
numMerges = 0;
while (p) {
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++) {
pSize++;
q = q.nextZ;
if (!q) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize === 0) {
e = q;
q = q.nextZ;
qSize--;
} else if (qSize === 0 || !q) {
e = p;
p = p.nextZ;
pSize--;
} else if (p.z <= q.z) {
e = p;
p = p.nextZ;
pSize--;
} else {
e = q;
q = q.nextZ;
qSize--;
}
if (tail) tail.nextZ = e;
else list = e;
e.prevZ = tail;
tail = e;
}
p = q;
}
tail.nextZ = null;
inSize *= 2;
} while (numMerges > 1);
return list;
}
// z-order of a point given coords and size of the data bounding box
function zOrder(x, y, minX, minY, size) {
// coords are transformed into non-negative 15-bit integer range
x = 32767 * (x - minX) / size;
y = 32767 * (y - minY) / size;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
function getLeftmost(start) {
var p = start,
leftmost = start;
do {
if (p.x < leftmost.x) leftmost = p;
p = p.next;
} while (p !== start);
return leftmost;
}
// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
}
// signed area of a triangle
function area(p, q, r) {
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}
// check if two points are equal
function equals(p1, p2) {
return p1.x === p2.x && p1.y === p2.y;
}
// check if two segments intersect
function intersects(p1, q1, p2, q2) {
if ((equals(p1, q1) && equals(p2, q2)) ||
(equals(p1, q2) && equals(p2, q1))) return true;
return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
}
// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
var p = a;
do {
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
intersects(p, p.next, a, b)) return true;
p = p.next;
} while (p !== a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
function locallyInside(a, b) {
return area(a.prev, a, a.next) < 0 ?
area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}
// check if the middle point of a polygon diagonal is inside the polygon
function middleInside(a, b) {
var p = a,
inside = false,
px = (a.x + b.x) / 2,
py = (a.y + b.y) / 2;
do {
if (((p.y > py) !== (p.next.y > py)) && (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
inside = !inside;
p = p.next;
} while (p !== a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon(a, b) {
var a2 = new Node(a.i, a.x, a.y),
b2 = new Node(b.i, b.x, b.y),
an = a.next,
bp = b.prev;
a.next = b;
b.prev = a;
a2.next = an;
an.prev = a2;
b2.next = a2;
a2.prev = b2;
bp.next = b2;
b2.prev = bp;
return b2;
}
// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode(i, x, y, last) {
var p = new Node(i, x, y);
if (!last) {
p.prev = p;
p.next = p;
} else {
p.next = last.next;
p.prev = last;
last.next.prev = p;
last.next = p;
}
return p;
}
function removeNode(p) {
p.next.prev = p.prev;
p.prev.next = p.next;
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
}
function Node(i, x, y) {
// vertice index in coordinates array
this.i = i;
// vertex coordinates
this.x = x;
this.y = y;
// previous and next vertice nodes in a polygon ring
this.prev = null;
this.next = null;
// z-order curve value
this.z = null;
// previous and next nodes in z-order
this.prevZ = null;
this.nextZ = null;
// indicates whether this is a steiner point
this.steiner = false;
}
// return a percentage difference between the polygon area and its triangulation area;
// used to verify correctness of triangulation
earcut.deviation = function (data, holeIndices, dim, triangles) {
var hasHoles = holeIndices && holeIndices.length;
var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
if (hasHoles) {
for (var i = 0, len = holeIndices.length; i < len; i++) {
var start = holeIndices[i] * dim;
var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
polygonArea -= Math.abs(signedArea(data, start, end, dim));
}
}
var trianglesArea = 0;
for (i = 0; i < triangles.length; i += 3) {
var a = triangles[i] * dim;
var b = triangles[i + 1] * dim;
var c = triangles[i + 2] * dim;
trianglesArea += Math.abs(
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
}
return polygonArea === 0 && trianglesArea === 0 ? 0 :
Math.abs((trianglesArea - polygonArea) / polygonArea);
};
function signedArea(data, start, end, dim) {
var sum = 0;
for (var i = start, j = end - dim; i < end; i += dim) {
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
j = i;
}
return sum;
}
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
earcut.flatten = function (data) {
var dim = data[0][0].length,
result = {vertices: [], holes: [], dimensions: dim},
holeIndex = 0;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
}
if (i > 0) {
holeIndex += data[i - 1].length;
result.holes.push(holeIndex);
}
}
return result;
};
return earcut;
});
/*global define*/
define('Core/WindingOrder',[
'./freezeObject',
'./WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Winding order defines the order of vertices for a triangle to be considered front-facing.
*
* @exports WindingOrder
*/
var WindingOrder = {
/**
* Vertices are in clockwise order.
*
* @type {Number}
* @constant
*/
CLOCKWISE : WebGLConstants.CW,
/**
* Vertices are in counter-clockwise order.
*
* @type {Number}
* @constant
*/
COUNTER_CLOCKWISE : WebGLConstants.CCW,
/**
* @private
*/
validate : function(windingOrder) {
return windingOrder === WindingOrder.CLOCKWISE ||
windingOrder === WindingOrder.COUNTER_CLOCKWISE;
}
};
return freezeObject(WindingOrder);
});
/*global define*/
define('Core/PolygonPipeline',[
'../ThirdParty/earcut-2.1.1',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./Math',
'./PrimitiveType',
'./WindingOrder'
], function(
earcut,
Cartesian2,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
CesiumMath,
PrimitiveType,
WindingOrder) {
'use strict';
var scaleToGeodeticHeightN = new Cartesian3();
var scaleToGeodeticHeightP = new Cartesian3();
/**
* @private
*/
var PolygonPipeline = {};
/**
* @exception {DeveloperError} At least three positions are required.
*/
PolygonPipeline.computeArea2D = function(positions) {
if (!defined(positions)) {
throw new DeveloperError('positions is required.');
}
if (positions.length < 3) {
throw new DeveloperError('At least three positions are required.');
}
var length = positions.length;
var area = 0.0;
for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) {
var v0 = positions[i0];
var v1 = positions[i1];
area += (v0.x * v1.y) - (v1.x * v0.y);
}
return area * 0.5;
};
/**
* @returns {WindingOrder} The winding order.
*
* @exception {DeveloperError} At least three positions are required.
*/
PolygonPipeline.computeWindingOrder2D = function(positions) {
var area = PolygonPipeline.computeArea2D(positions);
return (area > 0.0) ? WindingOrder.COUNTER_CLOCKWISE : WindingOrder.CLOCKWISE;
};
/**
* Triangulate a polygon.
*
* @param {Cartesian2[]} positions Cartesian2 array containing the vertices of the polygon
* @param {Number[]} [holes] An array of the staring indices of the holes.
* @returns {Number[]} Index array representing triangles that fill the polygon
*/
PolygonPipeline.triangulate = function(positions, holes) {
if (!defined(positions)) {
throw new DeveloperError('positions is required.');
}
var flattenedPositions = Cartesian2.packArray(positions);
return earcut(flattenedPositions, holes, 2);
};
var subdivisionV0Scratch = new Cartesian3();
var subdivisionV1Scratch = new Cartesian3();
var subdivisionV2Scratch = new Cartesian3();
var subdivisionS0Scratch = new Cartesian3();
var subdivisionS1Scratch = new Cartesian3();
var subdivisionS2Scratch = new Cartesian3();
var subdivisionMidScratch = new Cartesian3();
/**
* Subdivides positions and raises points to the surface of the ellipsoid.
*
* @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on.
* @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon.
* @param {Number[]} indices An array of indices that determines the triangles in the polygon.
* @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
*
* @exception {DeveloperError} At least three indices are required.
* @exception {DeveloperError} The number of indices must be divisable by three.
* @exception {DeveloperError} Granularity must be greater than zero.
*/
PolygonPipeline.computeSubdivision = function(ellipsoid, positions, indices, granularity) {
granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE);
if (!defined(ellipsoid)) {
throw new DeveloperError('ellipsoid is required.');
}
if (!defined(positions)) {
throw new DeveloperError('positions is required.');
}
if (!defined(indices)) {
throw new DeveloperError('indices is required.');
}
if (indices.length < 3) {
throw new DeveloperError('At least three indices are required.');
}
if (indices.length % 3 !== 0) {
throw new DeveloperError('The number of indices must be divisable by three.');
}
if (granularity <= 0.0) {
throw new DeveloperError('granularity must be greater than zero.');
}
// triangles that need (or might need) to be subdivided.
var triangles = indices.slice(0);
// New positions due to edge splits are appended to the positions list.
var i;
var length = positions.length;
var subdividedPositions = new Array(length * 3);
var q = 0;
for (i = 0; i < length; i++) {
var item = positions[i];
subdividedPositions[q++] = item.x;
subdividedPositions[q++] = item.y;
subdividedPositions[q++] = item.z;
}
var subdividedIndices = [];
// Used to make sure shared edges are not split more than once.
var edges = {};
var radius = ellipsoid.maximumRadius;
var minDistance = CesiumMath.chordLength(granularity, radius);
var minDistanceSqrd = minDistance * minDistance;
while (triangles.length > 0) {
var i2 = triangles.pop();
var i1 = triangles.pop();
var i0 = triangles.pop();
var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch);
var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch);
var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch);
var s0 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v0, subdivisionS0Scratch), radius, subdivisionS0Scratch);
var s1 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v1, subdivisionS1Scratch), radius, subdivisionS1Scratch);
var s2 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v2, subdivisionS2Scratch), radius, subdivisionS2Scratch);
var g0 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s0, s1, subdivisionMidScratch));
var g1 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s1, s2, subdivisionMidScratch));
var g2 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s2, s0, subdivisionMidScratch));
var max = Math.max(g0, g1, g2);
var edge;
var mid;
// if the max length squared of a triangle edge is greater than the chord length of squared
// of the granularity, subdivide the triangle
if (max > minDistanceSqrd) {
if (g0 === max) {
edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1);
i = edges[edge];
if (!defined(i)) {
mid = Cartesian3.add(v0, v1, subdivisionMidScratch);
Cartesian3.multiplyByScalar(mid, 0.5, mid);
subdividedPositions.push(mid.x, mid.y, mid.z);
i = subdividedPositions.length / 3 - 1;
edges[edge] = i;
}
triangles.push(i0, i, i2);
triangles.push(i, i1, i2);
} else if (g1 === max) {
edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2);
i = edges[edge];
if (!defined(i)) {
mid = Cartesian3.add(v1, v2, subdivisionMidScratch);
Cartesian3.multiplyByScalar(mid, 0.5, mid);
subdividedPositions.push(mid.x, mid.y, mid.z);
i = subdividedPositions.length / 3 - 1;
edges[edge] = i;
}
triangles.push(i1, i, i0);
triangles.push(i, i2, i0);
} else if (g2 === max) {
edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0);
i = edges[edge];
if (!defined(i)) {
mid = Cartesian3.add(v2, v0, subdivisionMidScratch);
Cartesian3.multiplyByScalar(mid, 0.5, mid);
subdividedPositions.push(mid.x, mid.y, mid.z);
i = subdividedPositions.length / 3 - 1;
edges[edge] = i;
}
triangles.push(i2, i, i1);
triangles.push(i, i0, i1);
}
} else {
subdividedIndices.push(i0);
subdividedIndices.push(i1);
subdividedIndices.push(i2);
}
}
return new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : subdividedPositions
})
},
indices : subdividedIndices,
primitiveType : PrimitiveType.TRIANGLES
});
};
/**
* Scales each position of a geometry's position attribute to a height, in place.
*
* @param {Number[]} positions The array of numbers representing the positions to be scaled
* @param {Number} [height=0.0] The desired height to add to the positions
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
* @param {Boolean} [scaleToSurface=true] true
if the positions need to be scaled to the surface before the height is added.
* @returns {Number[]} The input array of positions, scaled to height
*/
PolygonPipeline.scaleToGeodeticHeight = function(positions, height, ellipsoid, scaleToSurface) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var n = scaleToGeodeticHeightN;
var p = scaleToGeodeticHeightP;
height = defaultValue(height, 0.0);
scaleToSurface = defaultValue(scaleToSurface, true);
if (defined(positions)) {
var length = positions.length;
for ( var i = 0; i < length; i += 3) {
Cartesian3.fromArray(positions, i, p);
if (scaleToSurface) {
p = ellipsoid.scaleToGeodeticSurface(p, p);
}
if (height !== 0) {
n = ellipsoid.geodeticSurfaceNormal(p, n);
Cartesian3.multiplyByScalar(n, height, n);
Cartesian3.add(p, n, p);
}
positions[i] = p.x;
positions[i + 1] = p.y;
positions[i + 2] = p.z;
}
}
return positions;
};
return PolygonPipeline;
});
/*global define*/
define('Core/CorridorGeometry',[
'./arrayRemoveDuplicates',
'./BoundingSphere',
'./Cartesian3',
'./Cartographic',
'./ComponentDatatype',
'./CornerType',
'./CorridorGeometryLibrary',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PolygonPipeline',
'./PrimitiveType',
'./Rectangle',
'./VertexFormat'
], function(
arrayRemoveDuplicates,
BoundingSphere,
Cartesian3,
Cartographic,
ComponentDatatype,
CornerType,
CorridorGeometryLibrary,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PolygonPipeline,
PrimitiveType,
Rectangle,
VertexFormat) {
'use strict';
var cartesian1 = new Cartesian3();
var cartesian2 = new Cartesian3();
var cartesian3 = new Cartesian3();
var cartesian4 = new Cartesian3();
var cartesian5 = new Cartesian3();
var cartesian6 = new Cartesian3();
var scratch1 = new Cartesian3();
var scratch2 = new Cartesian3();
function addNormals(attr, normal, left, front, back, vertexFormat) {
var normals = attr.normals;
var tangents = attr.tangents;
var binormals = attr.binormals;
var forward = Cartesian3.normalize(Cartesian3.cross(left, normal, scratch1), scratch1);
if (vertexFormat.normal) {
CorridorGeometryLibrary.addAttribute(normals, normal, front, back);
}
if (vertexFormat.binormal) {
CorridorGeometryLibrary.addAttribute(binormals, left, front, back);
}
if (vertexFormat.tangent) {
CorridorGeometryLibrary.addAttribute(tangents, forward, front, back);
}
}
function combine(computedPositions, vertexFormat, ellipsoid) {
var positions = computedPositions.positions;
var corners = computedPositions.corners;
var endPositions = computedPositions.endPositions;
var computedLefts = computedPositions.lefts;
var computedNormals = computedPositions.normals;
var attributes = new GeometryAttributes();
var corner;
var leftCount = 0;
var rightCount = 0;
var i;
var indicesLength = 0;
var length;
for (i = 0; i < positions.length; i += 2) {
length = positions[i].length - 3;
leftCount += length; //subtracting 3 to account for duplicate points at corners
indicesLength += length*2;
rightCount += positions[i + 1].length - 3;
}
leftCount += 3; //add back count for end positions
rightCount += 3;
for (i = 0; i < corners.length; i++) {
corner = corners[i];
var leftSide = corners[i].leftPositions;
if (defined(leftSide)) {
length = leftSide.length;
leftCount += length;
indicesLength += length;
} else {
length = corners[i].rightPositions.length;
rightCount += length;
indicesLength += length;
}
}
var addEndPositions = defined(endPositions);
var endPositionLength;
if (addEndPositions) {
endPositionLength = endPositions[0].length - 3;
leftCount += endPositionLength;
rightCount += endPositionLength;
endPositionLength /= 3;
indicesLength += endPositionLength * 6;
}
var size = leftCount + rightCount;
var finalPositions = new Float64Array(size);
var normals = (vertexFormat.normal) ? new Float32Array(size) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(size) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(size) : undefined;
var attr = {
normals : normals,
tangents : tangents,
binormals : binormals
};
var front = 0;
var back = size - 1;
var UL, LL, UR, LR;
var normal = cartesian1;
var left = cartesian2;
var rightPos, leftPos;
var halfLength = endPositionLength / 2;
var indices = IndexDatatype.createTypedArray(size / 3, indicesLength);
var index = 0;
if (addEndPositions) { // add rounded end
leftPos = cartesian3;
rightPos = cartesian4;
var firstEndPositions = endPositions[0];
normal = Cartesian3.fromArray(computedNormals, 0, normal);
left = Cartesian3.fromArray(computedLefts, 0, left);
for (i = 0; i < halfLength; i++) {
leftPos = Cartesian3.fromArray(firstEndPositions, (halfLength - 1 - i) * 3, leftPos);
rightPos = Cartesian3.fromArray(firstEndPositions, (halfLength + i) * 3, rightPos);
CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
addNormals(attr, normal, left, front, back, vertexFormat);
LL = front / 3;
LR = LL + 1;
UL = (back - 2) / 3;
UR = UL - 1;
indices[index++] = UL;
indices[index++] = LL;
indices[index++] = UR;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
}
var posIndex = 0;
var compIndex = 0;
var rightEdge = positions[posIndex++]; //add first two edges
var leftEdge = positions[posIndex++];
finalPositions.set(rightEdge, front);
finalPositions.set(leftEdge, back - leftEdge.length + 1);
left = Cartesian3.fromArray(computedLefts, compIndex, left);
var rightNormal;
var leftNormal;
length = leftEdge.length - 3;
for (i = 0; i < length; i += 3) {
rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, i, scratch1), scratch1);
leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length - i, scratch2), scratch2);
normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
addNormals(attr, normal, left, front, back, vertexFormat);
LL = front / 3;
LR = LL + 1;
UL = (back - 2) / 3;
UR = UL - 1;
indices[index++] = UL;
indices[index++] = LL;
indices[index++] = UR;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, length, scratch1), scratch1);
leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length, scratch2), scratch2);
normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
compIndex += 3;
for (i = 0; i < corners.length; i++) {
var j;
corner = corners[i];
var l = corner.leftPositions;
var r = corner.rightPositions;
var pivot;
var start;
var outsidePoint = cartesian6;
var previousPoint = cartesian3;
var nextPoint = cartesian4;
normal = Cartesian3.fromArray(computedNormals, compIndex, normal);
if (defined(l)) {
addNormals(attr, normal, left, undefined, back, vertexFormat);
back -= 3;
pivot = LR;
start = UR;
for (j = 0; j < l.length / 3; j++) {
outsidePoint = Cartesian3.fromArray(l, j * 3, outsidePoint);
indices[index++] = pivot;
indices[index++] = start - j - 1;
indices[index++] = start - j;
CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, undefined, back);
previousPoint = Cartesian3.fromArray(finalPositions, (start - j - 1) * 3, previousPoint);
nextPoint = Cartesian3.fromArray(finalPositions, pivot * 3, nextPoint);
left = Cartesian3.normalize(Cartesian3.subtract(previousPoint, nextPoint, left), left);
addNormals(attr, normal, left, undefined, back, vertexFormat);
back -= 3;
}
outsidePoint = Cartesian3.fromArray(finalPositions, pivot * 3, outsidePoint);
previousPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start) * 3, previousPoint), outsidePoint, previousPoint);
nextPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start - j) * 3, nextPoint), outsidePoint, nextPoint);
left = Cartesian3.normalize(Cartesian3.add(previousPoint, nextPoint, left), left);
addNormals(attr, normal, left, front, undefined, vertexFormat);
front += 3;
} else {
addNormals(attr, normal, left, front, undefined, vertexFormat);
front += 3;
pivot = UR;
start = LR;
for (j = 0; j < r.length / 3; j++) {
outsidePoint = Cartesian3.fromArray(r, j * 3, outsidePoint);
indices[index++] = pivot;
indices[index++] = start + j;
indices[index++] = start + j + 1;
CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, front);
previousPoint = Cartesian3.fromArray(finalPositions, pivot * 3, previousPoint);
nextPoint = Cartesian3.fromArray(finalPositions, (start + j) * 3, nextPoint);
left = Cartesian3.normalize(Cartesian3.subtract(previousPoint, nextPoint, left), left);
addNormals(attr, normal, left, front, undefined, vertexFormat);
front += 3;
}
outsidePoint = Cartesian3.fromArray(finalPositions, pivot * 3, outsidePoint);
previousPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start + j) * 3, previousPoint), outsidePoint, previousPoint);
nextPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, start * 3, nextPoint), outsidePoint, nextPoint);
left = Cartesian3.normalize(Cartesian3.negate(Cartesian3.add(nextPoint, previousPoint, left), left), left);
addNormals(attr, normal, left, undefined, back, vertexFormat);
back -= 3;
}
rightEdge = positions[posIndex++];
leftEdge = positions[posIndex++];
rightEdge.splice(0, 3); //remove duplicate points added by corner
leftEdge.splice(leftEdge.length - 3, 3);
finalPositions.set(rightEdge, front);
finalPositions.set(leftEdge, back - leftEdge.length + 1);
length = leftEdge.length - 3;
compIndex += 3;
left = Cartesian3.fromArray(computedLefts, compIndex, left);
for (j = 0; j < leftEdge.length; j += 3) {
rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, j, scratch1), scratch1);
leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length - j, scratch2), scratch2);
normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
addNormals(attr, normal, left, front, back, vertexFormat);
LR = front / 3;
LL = LR - 1;
UR = (back - 2) / 3;
UL = UR + 1;
indices[index++] = UL;
indices[index++] = LL;
indices[index++] = UR;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
front -= 3;
back += 3;
}
normal = Cartesian3.fromArray(computedNormals, computedNormals.length - 3, normal);
addNormals(attr, normal, left, front, back, vertexFormat);
if (addEndPositions) { // add rounded end
front += 3;
back -= 3;
leftPos = cartesian3;
rightPos = cartesian4;
var lastEndPositions = endPositions[1];
for (i = 0; i < halfLength; i++) {
leftPos = Cartesian3.fromArray(lastEndPositions, (endPositionLength - i - 1) * 3, leftPos);
rightPos = Cartesian3.fromArray(lastEndPositions, i * 3, rightPos);
CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
addNormals(attr, normal, left, front, back, vertexFormat);
LR = front / 3;
LL = LR - 1;
UR = (back - 2) / 3;
UL = UR + 1;
indices[index++] = UL;
indices[index++] = LL;
indices[index++] = UR;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
}
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : finalPositions
});
if (vertexFormat.st) {
var st = new Float32Array(size / 3 * 2);
var rightSt;
var leftSt;
var stIndex = 0;
if (addEndPositions) {
leftCount /= 3;
rightCount /= 3;
var theta = Math.PI / (endPositionLength + 1);
leftSt = 1 / (leftCount - endPositionLength + 1);
rightSt = 1 / (rightCount - endPositionLength + 1);
var a;
var halfEndPos = endPositionLength / 2;
for (i = halfEndPos + 1; i < endPositionLength + 1; i++) { // lower left rounded end
a = CesiumMath.PI_OVER_TWO + theta * i;
st[stIndex++] = rightSt * (1 + Math.cos(a));
st[stIndex++] = 0.5 * (1 + Math.sin(a));
}
for (i = 1; i < rightCount - endPositionLength + 1; i++) { // bottom edge
st[stIndex++] = i * rightSt;
st[stIndex++] = 0;
}
for (i = endPositionLength; i > halfEndPos; i--) { // lower right rounded end
a = CesiumMath.PI_OVER_TWO - i * theta;
st[stIndex++] = 1 - rightSt * (1 + Math.cos(a));
st[stIndex++] = 0.5 * (1 + Math.sin(a));
}
for (i = halfEndPos; i > 0; i--) { // upper right rounded end
a = CesiumMath.PI_OVER_TWO - theta * i;
st[stIndex++] = 1 - leftSt * (1 + Math.cos(a));
st[stIndex++] = 0.5 * (1 + Math.sin(a));
}
for (i = leftCount - endPositionLength; i > 0; i--) { // top edge
st[stIndex++] = i * leftSt;
st[stIndex++] = 1;
}
for (i = 1; i < halfEndPos + 1; i++) { // upper left rounded end
a = CesiumMath.PI_OVER_TWO + theta * i;
st[stIndex++] = leftSt * (1 + Math.cos(a));
st[stIndex++] = 0.5 * (1 + Math.sin(a));
}
} else {
leftCount /= 3;
rightCount /= 3;
leftSt = 1 / (leftCount - 1);
rightSt = 1 / (rightCount - 1);
for (i = 0; i < rightCount; i++) { // bottom edge
st[stIndex++] = i * rightSt;
st[stIndex++] = 0;
}
for (i = leftCount; i > 0; i--) { // top edge
st[stIndex++] = (i - 1) * leftSt;
st[stIndex++] = 1;
}
}
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : st
});
}
if (vertexFormat.normal) {
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : attr.normals
});
}
if (vertexFormat.tangent) {
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : attr.tangents
});
}
if (vertexFormat.binormal) {
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : attr.binormals
});
}
return {
attributes : attributes,
indices : indices
};
}
function extrudedAttributes(attributes, vertexFormat) {
if (!vertexFormat.normal && !vertexFormat.binormal && !vertexFormat.tangent && !vertexFormat.st) {
return attributes;
}
var positions = attributes.position.values;
var topNormals;
var topBinormals;
if (vertexFormat.normal || vertexFormat.binormal) {
topNormals = attributes.normal.values;
topBinormals = attributes.binormal.values;
}
var size = attributes.position.values.length / 18;
var threeSize = size * 3;
var twoSize = size * 2;
var sixSize = threeSize * 2;
var i;
if (vertexFormat.normal || vertexFormat.binormal || vertexFormat.tangent) {
var normals = (vertexFormat.normal) ? new Float32Array(threeSize * 6) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(threeSize * 6) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(threeSize * 6) : undefined;
var topPosition = cartesian1;
var bottomPosition = cartesian2;
var previousPosition = cartesian3;
var normal = cartesian4;
var tangent = cartesian5;
var binormal = cartesian6;
var attrIndex = sixSize;
for (i = 0; i < threeSize; i += 3) {
var attrIndexOffset = attrIndex + sixSize;
topPosition = Cartesian3.fromArray(positions, i, topPosition);
bottomPosition = Cartesian3.fromArray(positions, i + threeSize, bottomPosition);
previousPosition = Cartesian3.fromArray(positions, (i + 3) % threeSize, previousPosition);
bottomPosition = Cartesian3.subtract(bottomPosition, topPosition, bottomPosition);
previousPosition = Cartesian3.subtract(previousPosition, topPosition, previousPosition);
normal = Cartesian3.normalize(Cartesian3.cross(bottomPosition, previousPosition, normal), normal);
if (vertexFormat.normal) {
CorridorGeometryLibrary.addAttribute(normals, normal, attrIndexOffset);
CorridorGeometryLibrary.addAttribute(normals, normal, attrIndexOffset + 3);
CorridorGeometryLibrary.addAttribute(normals, normal, attrIndex);
CorridorGeometryLibrary.addAttribute(normals, normal, attrIndex + 3);
}
if (vertexFormat.tangent || vertexFormat.binormal) {
binormal = Cartesian3.fromArray(topNormals, i, binormal);
if (vertexFormat.binormal) {
CorridorGeometryLibrary.addAttribute(binormals, binormal, attrIndexOffset);
CorridorGeometryLibrary.addAttribute(binormals, binormal, attrIndexOffset + 3);
CorridorGeometryLibrary.addAttribute(binormals, binormal, attrIndex);
CorridorGeometryLibrary.addAttribute(binormals, binormal, attrIndex + 3);
}
if (vertexFormat.tangent) {
tangent = Cartesian3.normalize(Cartesian3.cross(binormal, normal, tangent), tangent);
CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndexOffset);
CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndexOffset + 3);
CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndex);
CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndex + 3);
}
}
attrIndex += 6;
}
if (vertexFormat.normal) {
normals.set(topNormals); //top
for (i = 0; i < threeSize; i += 3) { //bottom normals
normals[i + threeSize] = -topNormals[i];
normals[i + threeSize + 1] = -topNormals[i + 1];
normals[i + threeSize + 2] = -topNormals[i + 2];
}
attributes.normal.values = normals;
} else {
attributes.normal = undefined;
}
if (vertexFormat.binormal) {
binormals.set(topBinormals); //top
binormals.set(topBinormals, threeSize); //bottom
attributes.binormal.values = binormals;
} else {
attributes.binormal = undefined;
}
if (vertexFormat.tangent) {
var topTangents = attributes.tangent.values;
tangents.set(topTangents); //top
tangents.set(topTangents, threeSize); //bottom
attributes.tangent.values = tangents;
}
}
if (vertexFormat.st) {
var topSt = attributes.st.values;
var st = new Float32Array(twoSize * 6);
st.set(topSt); //top
st.set(topSt, twoSize); //bottom
var index = twoSize * 2;
for ( var j = 0; j < 2; j++) {
st[index++] = topSt[0];
st[index++] = topSt[1];
for (i = 2; i < twoSize; i += 2) {
var s = topSt[i];
var t = topSt[i + 1];
st[index++] = s;
st[index++] = t;
st[index++] = s;
st[index++] = t;
}
st[index++] = topSt[0];
st[index++] = topSt[1];
}
attributes.st.values = st;
}
return attributes;
}
function addWallPositions(positions, index, wallPositions) {
wallPositions[index++] = positions[0];
wallPositions[index++] = positions[1];
wallPositions[index++] = positions[2];
for ( var i = 3; i < positions.length; i += 3) {
var x = positions[i];
var y = positions[i + 1];
var z = positions[i + 2];
wallPositions[index++] = x;
wallPositions[index++] = y;
wallPositions[index++] = z;
wallPositions[index++] = x;
wallPositions[index++] = y;
wallPositions[index++] = z;
}
wallPositions[index++] = positions[0];
wallPositions[index++] = positions[1];
wallPositions[index++] = positions[2];
return wallPositions;
}
function computePositionsExtruded(params, vertexFormat) {
var topVertexFormat = new VertexFormat({
position : vertexFormat.positon,
normal : (vertexFormat.normal || vertexFormat.binormal),
tangent : vertexFormat.tangent,
binormal : (vertexFormat.normal || vertexFormat.binormal),
st : vertexFormat.st
});
var ellipsoid = params.ellipsoid;
var computedPositions = CorridorGeometryLibrary.computePositions(params);
var attr = combine(computedPositions, topVertexFormat, ellipsoid);
var height = params.height;
var extrudedHeight = params.extrudedHeight;
var attributes = attr.attributes;
var indices = attr.indices;
var positions = attributes.position.values;
var length = positions.length;
var newPositions = new Float64Array(length * 6);
var extrudedPositions = new Float64Array(length);
extrudedPositions.set(positions);
var wallPositions = new Float64Array(length * 4);
positions = PolygonPipeline.scaleToGeodeticHeight(positions, height, ellipsoid);
wallPositions = addWallPositions(positions, 0, wallPositions);
extrudedPositions = PolygonPipeline.scaleToGeodeticHeight(extrudedPositions, extrudedHeight, ellipsoid);
wallPositions = addWallPositions(extrudedPositions, length * 2, wallPositions);
newPositions.set(positions);
newPositions.set(extrudedPositions, length);
newPositions.set(wallPositions, length * 2);
attributes.position.values = newPositions;
length /= 3;
var i;
var iLength = indices.length;
var twoLength = length + length;
var newIndices = IndexDatatype.createTypedArray(newPositions.length / 3, iLength * 2 + twoLength * 3);
newIndices.set(indices);
var index = iLength;
for (i = 0; i < iLength; i += 3) { // bottom indices
var v0 = indices[i];
var v1 = indices[i + 1];
var v2 = indices[i + 2];
newIndices[index++] = v2 + length;
newIndices[index++] = v1 + length;
newIndices[index++] = v0 + length;
}
attributes = extrudedAttributes(attributes, vertexFormat);
var UL, LL, UR, LR;
for (i = 0; i < twoLength; i += 2) { //wall indices
UL = i + twoLength;
LL = UL + twoLength;
UR = UL + 1;
LR = LL + 1;
newIndices[index++] = UL;
newIndices[index++] = LL;
newIndices[index++] = UR;
newIndices[index++] = UR;
newIndices[index++] = LL;
newIndices[index++] = LR;
}
return {
attributes : attributes,
indices : newIndices
};
}
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartographic = new Cartographic();
function computeOffsetPoints(position1, position2, ellipsoid, halfWidth, min, max) {
// Compute direction of offset the point
var direction = Cartesian3.subtract(position2, position1, scratchCartesian1);
Cartesian3.normalize(direction, direction);
var normal = ellipsoid.geodeticSurfaceNormal(position1, scratchCartesian2);
var offsetDirection = Cartesian3.cross(direction, normal, scratchCartesian1);
Cartesian3.multiplyByScalar(offsetDirection, halfWidth, offsetDirection);
var minLat = min.latitude;
var minLon = min.longitude;
var maxLat = max.latitude;
var maxLon = max.longitude;
// Compute 2 offset points
Cartesian3.add(position1, offsetDirection, scratchCartesian2);
ellipsoid.cartesianToCartographic(scratchCartesian2, scratchCartographic);
var lat = scratchCartographic.latitude;
var lon = scratchCartographic.longitude;
minLat = Math.min(minLat, lat);
minLon = Math.min(minLon, lon);
maxLat = Math.max(maxLat, lat);
maxLon = Math.max(maxLon, lon);
Cartesian3.subtract(position1, offsetDirection, scratchCartesian2);
ellipsoid.cartesianToCartographic(scratchCartesian2, scratchCartographic);
lat = scratchCartographic.latitude;
lon = scratchCartographic.longitude;
minLat = Math.min(minLat, lat);
minLon = Math.min(minLon, lon);
maxLat = Math.max(maxLat, lat);
maxLon = Math.max(maxLon, lon);
min.latitude = minLat;
min.longitude = minLon;
max.latitude = maxLat;
max.longitude = maxLon;
}
var scratchCartesianOffset = new Cartesian3();
var scratchCartesianEnds = new Cartesian3();
var scratchCartographicMin = new Cartographic();
var scratchCartographicMax = new Cartographic();
function computeRectangle(positions, ellipsoid, width, cornerType) {
var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
var length = cleanPositions.length;
if (length < 2 || width <= 0) {
return new Rectangle();
}
var halfWidth = width * 0.5;
scratchCartographicMin.latitude = Number.POSITIVE_INFINITY;
scratchCartographicMin.longitude = Number.POSITIVE_INFINITY;
scratchCartographicMax.latitude = Number.NEGATIVE_INFINITY;
scratchCartographicMax.longitude = Number.NEGATIVE_INFINITY;
var lat, lon;
if (cornerType === CornerType.ROUNDED) {
// Compute start cap
var first = cleanPositions[0];
Cartesian3.subtract(first, cleanPositions[1], scratchCartesianOffset);
Cartesian3.normalize(scratchCartesianOffset, scratchCartesianOffset);
Cartesian3.multiplyByScalar(scratchCartesianOffset, halfWidth, scratchCartesianOffset);
Cartesian3.add(first, scratchCartesianOffset, scratchCartesianEnds);
ellipsoid.cartesianToCartographic(scratchCartesianEnds, scratchCartographic);
lat = scratchCartographic.latitude;
lon = scratchCartographic.longitude;
scratchCartographicMin.latitude = Math.min(scratchCartographicMin.latitude, lat);
scratchCartographicMin.longitude = Math.min(scratchCartographicMin.longitude, lon);
scratchCartographicMax.latitude = Math.max(scratchCartographicMax.latitude, lat);
scratchCartographicMax.longitude = Math.max(scratchCartographicMax.longitude, lon);
}
// Compute the rest
for (var i = 0; i < length-1; ++i) {
computeOffsetPoints(cleanPositions[i], cleanPositions[i+1], ellipsoid, halfWidth,
scratchCartographicMin, scratchCartographicMax);
}
// Compute ending point
var last = cleanPositions[length-1];
Cartesian3.subtract(last, cleanPositions[length-2], scratchCartesianOffset);
Cartesian3.normalize(scratchCartesianOffset, scratchCartesianOffset);
Cartesian3.multiplyByScalar(scratchCartesianOffset, halfWidth, scratchCartesianOffset);
Cartesian3.add(last, scratchCartesianOffset, scratchCartesianEnds);
computeOffsetPoints(last, scratchCartesianEnds, ellipsoid, halfWidth,
scratchCartographicMin, scratchCartographicMax);
if (cornerType === CornerType.ROUNDED) {
// Compute end cap
ellipsoid.cartesianToCartographic(scratchCartesianEnds, scratchCartographic);
lat = scratchCartographic.latitude;
lon = scratchCartographic.longitude;
scratchCartographicMin.latitude = Math.min(scratchCartographicMin.latitude, lat);
scratchCartographicMin.longitude = Math.min(scratchCartographicMin.longitude, lon);
scratchCartographicMax.latitude = Math.max(scratchCartographicMax.latitude, lat);
scratchCartographicMax.longitude = Math.max(scratchCartographicMax.longitude, lon);
}
var rectangle = new Rectangle();
rectangle.north = scratchCartographicMax.latitude;
rectangle.south = scratchCartographicMin.latitude;
rectangle.east = scratchCartographicMax.longitude;
rectangle.west = scratchCartographicMin.longitude;
return rectangle;
}
/**
* A description of a corridor. Corridor geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
*
* @alias CorridorGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of positions that define the center of the corridor.
* @param {Number} options.width The distance between the edges of the corridor in meters.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Number} [options.height=0] The distance in meters between the ellipsoid surface and the positions.
* @param {Number} [options.extrudedHeight] The distance in meters between the ellipsoid surface and the extruded face.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
*
* @see CorridorGeometry.createGeometry
* @see Packable
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo}
*
* @example
* var corridor = new Cesium.CorridorGeometry({
* vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
* positions : Cesium.Cartesian3.fromDegreesArray([-72.0, 40.0, -70.0, 35.0]),
* width : 100000
* });
*/
function CorridorGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.positions;
var width = options.width;
if (!defined(positions)) {
throw new DeveloperError('options.positions is required.');
}
if (!defined(width)) {
throw new DeveloperError('options.width is required.');
}
this._positions = positions;
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
this._width = width;
this._height = defaultValue(options.height, 0);
this._extrudedHeight = defaultValue(options.extrudedHeight, this._height);
this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
this._workerName = 'createCorridorGeometry';
this._rectangle = computeRectangle(positions, this._ellipsoid, width, this._cornerType);
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = 1 + positions.length * Cartesian3.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 5;
}
/**
* Stores the provided instance into the provided array.
*
* @param {CorridorGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
CorridorGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
Rectangle.pack(value._rectangle, array, startingIndex);
startingIndex += Rectangle.packedLength;
array[startingIndex++] = value._width;
array[startingIndex++] = value._height;
array[startingIndex++] = value._extrudedHeight;
array[startingIndex++] = value._cornerType;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchVertexFormat = new VertexFormat();
var scratchRectangle = new Rectangle();
var scratchOptions = {
positions : undefined,
ellipsoid : scratchEllipsoid,
vertexFormat : scratchVertexFormat,
width : undefined,
height : undefined,
extrudedHeight : undefined,
cornerType : undefined,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {CorridorGeometry} [result] The object into which to store the result.
* @returns {CorridorGeometry} The modified result parameter or a new CorridorGeometry instance if one was not provided.
*/
CorridorGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var length = array[startingIndex++];
var positions = new Array(length);
for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
startingIndex += Rectangle.packedLength;
var width = array[startingIndex++];
var height = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var cornerType = array[startingIndex++];
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.positions = positions;
scratchOptions.width = width;
scratchOptions.height = height;
scratchOptions.extrudedHeight = extrudedHeight;
scratchOptions.cornerType = cornerType;
scratchOptions.granularity = granularity;
return new CorridorGeometry(scratchOptions);
}
result._positions = positions;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._width = width;
result._height = height;
result._extrudedHeight = extrudedHeight;
result._cornerType = cornerType;
result._granularity = granularity;
result._rectangle = Rectangle.clone(rectangle);
return result;
};
/**
* Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere.
*
* @param {CorridorGeometry} corridorGeometry A description of the corridor.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
CorridorGeometry.createGeometry = function(corridorGeometry) {
var positions = corridorGeometry._positions;
var height = corridorGeometry._height;
var width = corridorGeometry._width;
var extrudedHeight = corridorGeometry._extrudedHeight;
var extrude = (height !== extrudedHeight);
var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
if ((cleanPositions.length < 2) || (width <= 0)) {
return;
}
var ellipsoid = corridorGeometry._ellipsoid;
var vertexFormat = corridorGeometry._vertexFormat;
var params = {
ellipsoid : ellipsoid,
positions : cleanPositions,
width : width,
cornerType : corridorGeometry._cornerType,
granularity : corridorGeometry._granularity,
saveAttributes: true
};
var attr;
if (extrude) {
var h = Math.max(height, extrudedHeight);
extrudedHeight = Math.min(height, extrudedHeight);
height = h;
params.height = height;
params.extrudedHeight = extrudedHeight;
attr = computePositionsExtruded(params, vertexFormat);
} else {
var computedPositions = CorridorGeometryLibrary.computePositions(params);
attr = combine(computedPositions, vertexFormat, ellipsoid);
attr.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(attr.attributes.position.values, height, ellipsoid);
}
var attributes = attr.attributes;
var boundingSphere = BoundingSphere.fromVertices(attributes.position.values, undefined, 3);
if (!vertexFormat.position) {
attr.attributes.position.values = undefined;
}
return new Geometry({
attributes : attributes,
indices : attr.indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : boundingSphere
});
};
/**
* @private
*/
CorridorGeometry.createShadowVolume = function(corridorGeometry, minHeightFunc, maxHeightFunc) {
var granularity = corridorGeometry._granularity;
var ellipsoid = corridorGeometry._ellipsoid;
var minHeight = minHeightFunc(granularity, ellipsoid);
var maxHeight = maxHeightFunc(granularity, ellipsoid);
return new CorridorGeometry({
positions : corridorGeometry._positions,
width : corridorGeometry._width,
cornerType : corridorGeometry._cornerType,
ellipsoid : ellipsoid,
granularity : granularity,
extrudedHeight : minHeight,
height : maxHeight,
vertexFormat : VertexFormat.POSITION_ONLY
});
};
defineProperties(CorridorGeometry.prototype, {
/**
* @private
*/
rectangle : {
get : function() {
return this._rectangle;
}
}
});
return CorridorGeometry;
});
/*global define*/
define('Core/CorridorOutlineGeometry',[
'./arrayRemoveDuplicates',
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./CornerType',
'./CorridorGeometryLibrary',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PolygonPipeline',
'./PrimitiveType'
], function(
arrayRemoveDuplicates,
BoundingSphere,
Cartesian3,
ComponentDatatype,
CornerType,
CorridorGeometryLibrary,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PolygonPipeline,
PrimitiveType) {
'use strict';
var cartesian1 = new Cartesian3();
var cartesian2 = new Cartesian3();
var cartesian3 = new Cartesian3();
function combine(computedPositions, cornerType) {
var wallIndices = [];
var positions = computedPositions.positions;
var corners = computedPositions.corners;
var endPositions = computedPositions.endPositions;
var attributes = new GeometryAttributes();
var corner;
var leftCount = 0;
var rightCount = 0;
var i;
var indicesLength = 0;
var length;
for (i = 0; i < positions.length; i += 2) {
length = positions[i].length - 3;
leftCount += length; //subtracting 3 to account for duplicate points at corners
indicesLength += length / 3 * 4;
rightCount += positions[i + 1].length - 3;
}
leftCount += 3; //add back count for end positions
rightCount += 3;
for (i = 0; i < corners.length; i++) {
corner = corners[i];
var leftSide = corners[i].leftPositions;
if (defined(leftSide)) {
length = leftSide.length;
leftCount += length;
indicesLength += length / 3 * 2;
} else {
length = corners[i].rightPositions.length;
rightCount += length;
indicesLength += length / 3 * 2;
}
}
var addEndPositions = defined(endPositions);
var endPositionLength;
if (addEndPositions) {
endPositionLength = endPositions[0].length - 3;
leftCount += endPositionLength;
rightCount += endPositionLength;
endPositionLength /= 3;
indicesLength += endPositionLength * 4;
}
var size = leftCount + rightCount;
var finalPositions = new Float64Array(size);
var front = 0;
var back = size - 1;
var UL, LL, UR, LR;
var rightPos, leftPos;
var halfLength = endPositionLength / 2;
var indices = IndexDatatype.createTypedArray(size / 3, indicesLength + 4);
var index = 0;
indices[index++] = front / 3;
indices[index++] = (back - 2) / 3;
if (addEndPositions) { // add rounded end
wallIndices.push(front / 3);
leftPos = cartesian1;
rightPos = cartesian2;
var firstEndPositions = endPositions[0];
for (i = 0; i < halfLength; i++) {
leftPos = Cartesian3.fromArray(firstEndPositions, (halfLength - 1 - i) * 3, leftPos);
rightPos = Cartesian3.fromArray(firstEndPositions, (halfLength + i) * 3, rightPos);
CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
LL = front / 3;
LR = LL + 1;
UL = (back - 2) / 3;
UR = UL - 1;
indices[index++] = UL;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
}
var posIndex = 0;
var rightEdge = positions[posIndex++]; //add first two edges
var leftEdge = positions[posIndex++];
finalPositions.set(rightEdge, front);
finalPositions.set(leftEdge, back - leftEdge.length + 1);
length = leftEdge.length - 3;
wallIndices.push(front / 3, (back - 2) / 3);
for (i = 0; i < length; i += 3) {
LL = front / 3;
LR = LL + 1;
UL = (back - 2) / 3;
UR = UL - 1;
indices[index++] = UL;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
for (i = 0; i < corners.length; i++) {
var j;
corner = corners[i];
var l = corner.leftPositions;
var r = corner.rightPositions;
var start;
var outsidePoint = cartesian3;
if (defined(l)) {
back -= 3;
start = UR;
wallIndices.push(LR);
for (j = 0; j < l.length / 3; j++) {
outsidePoint = Cartesian3.fromArray(l, j * 3, outsidePoint);
indices[index++] = start - j - 1;
indices[index++] = start - j;
CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, undefined, back);
back -= 3;
}
wallIndices.push(start - Math.floor(l.length / 6));
if (cornerType === CornerType.BEVELED) {
wallIndices.push((back - 2) / 3 + 1);
}
front += 3;
} else {
front += 3;
start = LR;
wallIndices.push(UR);
for (j = 0; j < r.length / 3; j++) {
outsidePoint = Cartesian3.fromArray(r, j * 3, outsidePoint);
indices[index++] = start + j;
indices[index++] = start + j + 1;
CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, front);
front += 3;
}
wallIndices.push(start + Math.floor(r.length / 6));
if (cornerType === CornerType.BEVELED) {
wallIndices.push(front / 3 - 1);
}
back -= 3;
}
rightEdge = positions[posIndex++];
leftEdge = positions[posIndex++];
rightEdge.splice(0, 3); //remove duplicate points added by corner
leftEdge.splice(leftEdge.length - 3, 3);
finalPositions.set(rightEdge, front);
finalPositions.set(leftEdge, back - leftEdge.length + 1);
length = leftEdge.length - 3;
for (j = 0; j < leftEdge.length; j += 3) {
LR = front / 3;
LL = LR - 1;
UR = (back - 2) / 3;
UL = UR + 1;
indices[index++] = UL;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
front -= 3;
back += 3;
wallIndices.push(front / 3, (back - 2) / 3);
}
if (addEndPositions) { // add rounded end
front += 3;
back -= 3;
leftPos = cartesian1;
rightPos = cartesian2;
var lastEndPositions = endPositions[1];
for (i = 0; i < halfLength; i++) {
leftPos = Cartesian3.fromArray(lastEndPositions, (endPositionLength - i - 1) * 3, leftPos);
rightPos = Cartesian3.fromArray(lastEndPositions, i * 3, rightPos);
CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
LR = front / 3;
LL = LR - 1;
UR = (back - 2) / 3;
UL = UR + 1;
indices[index++] = UL;
indices[index++] = UR;
indices[index++] = LL;
indices[index++] = LR;
front += 3;
back -= 3;
}
wallIndices.push(front / 3);
} else {
wallIndices.push(front / 3, (back - 2) / 3);
}
indices[index++] = front / 3;
indices[index++] = (back - 2) / 3;
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : finalPositions
});
return {
attributes : attributes,
indices : indices,
wallIndices : wallIndices
};
}
function computePositionsExtruded(params) {
var ellipsoid = params.ellipsoid;
var computedPositions = CorridorGeometryLibrary.computePositions(params);
var attr = combine(computedPositions, params.cornerType);
var wallIndices = attr.wallIndices;
var height = params.height;
var extrudedHeight = params.extrudedHeight;
var attributes = attr.attributes;
var indices = attr.indices;
var positions = attributes.position.values;
var length = positions.length;
var extrudedPositions = new Float64Array(length);
extrudedPositions.set(positions);
var newPositions = new Float64Array(length * 2);
positions = PolygonPipeline.scaleToGeodeticHeight(positions, height, ellipsoid);
extrudedPositions = PolygonPipeline.scaleToGeodeticHeight(extrudedPositions, extrudedHeight, ellipsoid);
newPositions.set(positions);
newPositions.set(extrudedPositions, length);
attributes.position.values = newPositions;
length /= 3;
var i;
var iLength = indices.length;
var newIndices = IndexDatatype.createTypedArray(newPositions.length / 3, (iLength + wallIndices.length) * 2);
newIndices.set(indices);
var index = iLength;
for (i = 0; i < iLength; i += 2) { // bottom indices
var v0 = indices[i];
var v1 = indices[i + 1];
newIndices[index++] = v0 + length;
newIndices[index++] = v1 + length;
}
var UL, LL;
for (i = 0; i < wallIndices.length; i++) { //wall indices
UL = wallIndices[i];
LL = UL + length;
newIndices[index++] = UL;
newIndices[index++] = LL;
}
return {
attributes : attributes,
indices : newIndices
};
}
/**
* A description of a corridor outline.
*
* @alias CorridorOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of positions that define the center of the corridor outline.
* @param {Number} options.width The distance between the edges of the corridor outline.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Number} [options.height=0] The distance in meters between the positions and the ellipsoid surface.
* @param {Number} [options.extrudedHeight] The distance in meters between the extruded face and the ellipsoid surface.
* @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
*
* @see CorridorOutlineGeometry.createGeometry
*
* @example
* var corridor = new Cesium.CorridorOutlineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArray([-72.0, 40.0, -70.0, 35.0]),
* width : 100000
* });
*/
function CorridorOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.positions;
var width = options.width;
if (!defined(positions)) {
throw new DeveloperError('options.positions is required.');
}
if (!defined(width)) {
throw new DeveloperError('options.width is required.');
}
this._positions = positions;
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
this._width = width;
this._height = defaultValue(options.height, 0);
this._extrudedHeight = defaultValue(options.extrudedHeight, this._height);
this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
this._workerName = 'createCorridorOutlineGeometry';
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = 1 + positions.length * Cartesian3.packedLength + Ellipsoid.packedLength + 5;
}
/**
* Stores the provided instance into the provided array.
*
* @param {CorridorOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
CorridorOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex++] = value._width;
array[startingIndex++] = value._height;
array[startingIndex++] = value._extrudedHeight;
array[startingIndex++] = value._cornerType;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchOptions = {
positions : undefined,
ellipsoid : scratchEllipsoid,
width : undefined,
height : undefined,
extrudedHeight : undefined,
cornerType : undefined,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {CorridorOutlineGeometry} [result] The object into which to store the result.
* @returns {CorridorOutlineGeometry} The modified result parameter or a new CorridorOutlineGeometry instance if one was not provided.
*/
CorridorOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var length = array[startingIndex++];
var positions = new Array(length);
for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var width = array[startingIndex++];
var height = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var cornerType = array[startingIndex++];
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.positions = positions;
scratchOptions.width = width;
scratchOptions.height = height;
scratchOptions.extrudedHeight = extrudedHeight;
scratchOptions.cornerType = cornerType;
scratchOptions.granularity = granularity;
return new CorridorOutlineGeometry(scratchOptions);
}
result._positions = positions;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._width = width;
result._height = height;
result._extrudedHeight = extrudedHeight;
result._cornerType = cornerType;
result._granularity = granularity;
return result;
};
/**
* Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere.
*
* @param {CorridorOutlineGeometry} corridorOutlineGeometry A description of the corridor.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
CorridorOutlineGeometry.createGeometry = function(corridorOutlineGeometry) {
var positions = corridorOutlineGeometry._positions;
var height = corridorOutlineGeometry._height;
var width = corridorOutlineGeometry._width;
var extrudedHeight = corridorOutlineGeometry._extrudedHeight;
var extrude = (height !== extrudedHeight);
var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
if ((cleanPositions.length < 2) || (width <= 0)) {
return;
}
var ellipsoid = corridorOutlineGeometry._ellipsoid;
var params = {
ellipsoid : ellipsoid,
positions : cleanPositions,
width : width,
cornerType : corridorOutlineGeometry._cornerType,
granularity : corridorOutlineGeometry._granularity,
saveAttributes : false
};
var attr;
if (extrude) {
var h = Math.max(height, extrudedHeight);
extrudedHeight = Math.min(height, extrudedHeight);
height = h;
params.height = height;
params.extrudedHeight = extrudedHeight;
attr = computePositionsExtruded(params);
} else {
var computedPositions = CorridorGeometryLibrary.computePositions(params);
attr = combine(computedPositions, params.cornerType);
attr.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(attr.attributes.position.values, height, ellipsoid);
}
var attributes = attr.attributes;
var boundingSphere = BoundingSphere.fromVertices(attributes.position.values, undefined, 3);
return new Geometry({
attributes : attributes,
indices : attr.indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : boundingSphere
});
};
return CorridorOutlineGeometry;
});
/*global define*/
define('Core/createGuid',[],function() {
'use strict';
/**
* Creates a Globally unique identifier (GUID) string. A GUID is 128 bits long, and can guarantee uniqueness across space and time.
*
* @exports createGuid
*
* @returns {String}
*
*
* @example
* this.guid = Cesium.createGuid();
*
* @see {@link http://www.ietf.org/rfc/rfc4122.txt|RFC 4122 A Universally Unique IDentifier (UUID) URN Namespace}
*/
function createGuid() {
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0;
var v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
return createGuid;
});
/*global define*/
define('Core/CylinderGeometryLibrary',[
'./Math'
], function(
CesiumMath) {
'use strict';
/**
* @private
*/
var CylinderGeometryLibrary = {};
/**
* @private
*/
CylinderGeometryLibrary.computePositions = function(length, topRadius, bottomRadius, slices, fill){
var topZ = length * 0.5;
var bottomZ = -topZ;
var twoSlice = slices + slices;
var size = (fill) ? 2 * twoSlice : twoSlice;
var positions = new Float64Array(size*3);
var i;
var index = 0;
var tbIndex = 0;
var bottomOffset = (fill) ? twoSlice*3 : 0;
var topOffset = (fill) ? (twoSlice + slices)*3 : slices*3;
for (i = 0; i < slices; i++) {
var angle = i / slices * CesiumMath.TWO_PI;
var x = Math.cos(angle);
var y = Math.sin(angle);
var bottomX = x * bottomRadius;
var bottomY = y * bottomRadius;
var topX = x * topRadius;
var topY = y * topRadius;
positions[tbIndex + bottomOffset] = bottomX;
positions[tbIndex + bottomOffset + 1] = bottomY;
positions[tbIndex + bottomOffset + 2] = bottomZ;
positions[tbIndex + topOffset] = topX;
positions[tbIndex + topOffset + 1] = topY;
positions[tbIndex + topOffset + 2] = topZ;
tbIndex += 3;
if (fill) {
positions[index++] = bottomX;
positions[index++] = bottomY;
positions[index++] = bottomZ;
positions[index++] = topX;
positions[index++] = topY;
positions[index++] = topZ;
}
}
return positions;
};
return CylinderGeometryLibrary;
});
/*global define*/
define('Core/CylinderGeometry',[
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./CylinderGeometryLibrary',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PrimitiveType',
'./VertexFormat'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
ComponentDatatype,
CylinderGeometryLibrary,
defaultValue,
defined,
DeveloperError,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PrimitiveType,
VertexFormat) {
'use strict';
var radiusScratch = new Cartesian2();
var normalScratch = new Cartesian3();
var binormalScratch = new Cartesian3();
var tangentScratch = new Cartesian3();
var positionScratch = new Cartesian3();
/**
* A description of a cylinder.
*
* @alias CylinderGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Number} options.length The length of the cylinder.
* @param {Number} options.topRadius The radius of the top of the cylinder.
* @param {Number} options.bottomRadius The radius of the bottom of the cylinder.
* @param {Number} [options.slices=128] The number of edges around the perimeter of the cylinder.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
*
* @exception {DeveloperError} options.length must be greater than 0.
* @exception {DeveloperError} options.topRadius must be greater than 0.
* @exception {DeveloperError} options.bottomRadius must be greater than 0.
* @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0.
* @exception {DeveloperError} options.slices must be greater than or equal to 3.
*
* @see CylinderGeometry.createGeometry
*
* @example
* // create cylinder geometry
* var cylinder = new Cesium.CylinderGeometry({
* length: 200000,
* topRadius: 80000,
* bottomRadius: 200000,
* });
* var geometry = Cesium.CylinderGeometry.createGeometry(cylinder);
*/
function CylinderGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var length = options.length;
var topRadius = options.topRadius;
var bottomRadius = options.bottomRadius;
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
var slices = defaultValue(options.slices, 128);
if (!defined(length)) {
throw new DeveloperError('options.length must be defined.');
}
if (!defined(topRadius)) {
throw new DeveloperError('options.topRadius must be defined.');
}
if (!defined(bottomRadius)) {
throw new DeveloperError('options.bottomRadius must be defined.');
}
if (slices < 3) {
throw new DeveloperError('options.slices must be greater than or equal to 3.');
}
this._length = length;
this._topRadius = topRadius;
this._bottomRadius = bottomRadius;
this._vertexFormat = VertexFormat.clone(vertexFormat);
this._slices = slices;
this._workerName = 'createCylinderGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
CylinderGeometry.packedLength = VertexFormat.packedLength + 4;
/**
* Stores the provided instance into the provided array.
*
* @param {CylinderGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
CylinderGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
array[startingIndex++] = value._length;
array[startingIndex++] = value._topRadius;
array[startingIndex++] = value._bottomRadius;
array[startingIndex] = value._slices;
return array;
};
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
vertexFormat : scratchVertexFormat,
length : undefined,
topRadius : undefined,
bottomRadius : undefined,
slices : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {CylinderGeometry} [result] The object into which to store the result.
* @returns {CylinderGeometry} The modified result parameter or a new CylinderGeometry instance if one was not provided.
*/
CylinderGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var length = array[startingIndex++];
var topRadius = array[startingIndex++];
var bottomRadius = array[startingIndex++];
var slices = array[startingIndex];
if (!defined(result)) {
scratchOptions.length = length;
scratchOptions.topRadius = topRadius;
scratchOptions.bottomRadius = bottomRadius;
scratchOptions.slices = slices;
return new CylinderGeometry(scratchOptions);
}
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._length = length;
result._topRadius = topRadius;
result._bottomRadius = bottomRadius;
result._slices = slices;
return result;
};
/**
* Computes the geometric representation of a cylinder, including its vertices, indices, and a bounding sphere.
*
* @param {CylinderGeometry} cylinderGeometry A description of the cylinder.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
CylinderGeometry.createGeometry = function(cylinderGeometry) {
var length = cylinderGeometry._length;
var topRadius = cylinderGeometry._topRadius;
var bottomRadius = cylinderGeometry._bottomRadius;
var vertexFormat = cylinderGeometry._vertexFormat;
var slices = cylinderGeometry._slices;
if ((length <= 0) || (topRadius < 0) || (bottomRadius < 0) || ((topRadius === 0) && (bottomRadius === 0))) {
return;
}
var twoSlices = slices + slices;
var threeSlices = slices + twoSlices;
var numVertices = twoSlices + twoSlices;
var positions = CylinderGeometryLibrary.computePositions(length, topRadius, bottomRadius, slices, true);
var st = (vertexFormat.st) ? new Float32Array(numVertices * 2) : undefined;
var normals = (vertexFormat.normal) ? new Float32Array(numVertices * 3) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(numVertices * 3) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(numVertices * 3) : undefined;
var i;
var computeNormal = (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal);
if (computeNormal) {
var computeTangent = (vertexFormat.tangent || vertexFormat.binormal);
var normalIndex = 0;
var tangentIndex = 0;
var binormalIndex = 0;
var normal = normalScratch;
normal.z = 0;
var tangent = tangentScratch;
var binormal = binormalScratch;
for (i = 0; i < slices; i++) {
var angle = i / slices * CesiumMath.TWO_PI;
var x = Math.cos(angle);
var y = Math.sin(angle);
if (computeNormal) {
normal.x = x;
normal.y = y;
if (computeTangent) {
tangent = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent), tangent);
}
if (vertexFormat.normal) {
normals[normalIndex++] = x;
normals[normalIndex++] = y;
normals[normalIndex++] = 0;
normals[normalIndex++] = x;
normals[normalIndex++] = y;
normals[normalIndex++] = 0;
}
if (vertexFormat.tangent) {
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
}
if (vertexFormat.binormal) {
binormal = Cartesian3.normalize(Cartesian3.cross(normal, tangent, binormal), binormal);
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
}
}
}
for (i = 0; i < slices; i++) {
if (vertexFormat.normal) {
normals[normalIndex++] = 0;
normals[normalIndex++] = 0;
normals[normalIndex++] = -1;
}
if (vertexFormat.tangent) {
tangents[tangentIndex++] = 1;
tangents[tangentIndex++] = 0;
tangents[tangentIndex++] = 0;
}
if (vertexFormat.binormal) {
binormals[binormalIndex++] = 0;
binormals[binormalIndex++] = -1;
binormals[binormalIndex++] = 0;
}
}
for (i = 0; i < slices; i++) {
if (vertexFormat.normal) {
normals[normalIndex++] = 0;
normals[normalIndex++] = 0;
normals[normalIndex++] = 1;
}
if (vertexFormat.tangent) {
tangents[tangentIndex++] = 1;
tangents[tangentIndex++] = 0;
tangents[tangentIndex++] = 0;
}
if (vertexFormat.binormal) {
binormals[binormalIndex++] = 0;
binormals[binormalIndex++] = 1;
binormals[binormalIndex++] = 0;
}
}
}
var numIndices = 12 * slices - 12;
var indices = IndexDatatype.createTypedArray(numVertices, numIndices);
var index = 0;
var j = 0;
for (i = 0; i < slices - 1; i++) {
indices[index++] = j;
indices[index++] = j + 2;
indices[index++] = j + 3;
indices[index++] = j;
indices[index++] = j + 3;
indices[index++] = j + 1;
j += 2;
}
indices[index++] = twoSlices - 2;
indices[index++] = 0;
indices[index++] = 1;
indices[index++] = twoSlices - 2;
indices[index++] = 1;
indices[index++] = twoSlices - 1;
for (i = 1; i < slices - 1; i++) {
indices[index++] = twoSlices + i + 1;
indices[index++] = twoSlices + i;
indices[index++] = twoSlices;
}
for (i = 1; i < slices - 1; i++) {
indices[index++] = threeSlices;
indices[index++] = threeSlices + i;
indices[index++] = threeSlices + i + 1;
}
var textureCoordIndex = 0;
if (vertexFormat.st) {
var rad = Math.max(topRadius, bottomRadius);
for (i = 0; i < numVertices; i++) {
var position = Cartesian3.fromArray(positions, i * 3, positionScratch);
st[textureCoordIndex++] = (position.x + rad) / (2.0 * rad);
st[textureCoordIndex++] = (position.y + rad) / (2.0 * rad);
}
}
var attributes = new GeometryAttributes();
if (vertexFormat.position) {
attributes.position = new GeometryAttribute({
componentDatatype: ComponentDatatype.DOUBLE,
componentsPerAttribute: 3,
values: positions
});
}
if (vertexFormat.normal) {
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.tangent) {
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
if (vertexFormat.st) {
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : st
});
}
radiusScratch.x = length * 0.5;
radiusScratch.y = Math.max(bottomRadius, topRadius);
var boundingSphere = new BoundingSphere(Cartesian3.ZERO, Cartesian2.magnitude(radiusScratch));
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : boundingSphere
});
};
return CylinderGeometry;
});
/*global define*/
define('Core/CylinderOutlineGeometry',[
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./CylinderGeometryLibrary',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./PrimitiveType'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
ComponentDatatype,
CylinderGeometryLibrary,
defaultValue,
defined,
DeveloperError,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
PrimitiveType) {
'use strict';
var radiusScratch = new Cartesian2();
/**
* A description of the outline of a cylinder.
*
* @alias CylinderOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Number} options.length The length of the cylinder.
* @param {Number} options.topRadius The radius of the top of the cylinder.
* @param {Number} options.bottomRadius The radius of the bottom of the cylinder.
* @param {Number} [options.slices=128] The number of edges around the perimeter of the cylinder.
* @param {Number} [options.numberOfVerticalLines=16] Number of lines to draw between the top and bottom surfaces of the cylinder.
*
* @exception {DeveloperError} options.length must be greater than 0.
* @exception {DeveloperError} options.topRadius must be greater than 0.
* @exception {DeveloperError} options.bottomRadius must be greater than 0.
* @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0.
* @exception {DeveloperError} options.slices must be greater than or equal to 3.
*
* @see CylinderOutlineGeometry.createGeometry
*
* @example
* // create cylinder geometry
* var cylinder = new Cesium.CylinderOutlineGeometry({
* length: 200000,
* topRadius: 80000,
* bottomRadius: 200000,
* });
* var geometry = Cesium.CylinderOutlineGeometry.createGeometry(cylinder);
*/
function CylinderOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var length = options.length;
var topRadius = options.topRadius;
var bottomRadius = options.bottomRadius;
var slices = defaultValue(options.slices, 128);
var numberOfVerticalLines = Math.max(defaultValue(options.numberOfVerticalLines, 16), 0);
if (!defined(length)) {
throw new DeveloperError('options.length must be defined.');
}
if (!defined(topRadius)) {
throw new DeveloperError('options.topRadius must be defined.');
}
if (!defined(bottomRadius)) {
throw new DeveloperError('options.bottomRadius must be defined.');
}
if (slices < 3) {
throw new DeveloperError('options.slices must be greater than or equal to 3.');
}
this._length = length;
this._topRadius = topRadius;
this._bottomRadius = bottomRadius;
this._slices = slices;
this._numberOfVerticalLines = numberOfVerticalLines;
this._workerName = 'createCylinderOutlineGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
CylinderOutlineGeometry.packedLength = 5;
/**
* Stores the provided instance into the provided array.
*
* @param {CylinderOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
CylinderOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value._length;
array[startingIndex++] = value._topRadius;
array[startingIndex++] = value._bottomRadius;
array[startingIndex++] = value._slices;
array[startingIndex] = value._numberOfVerticalLines;
return array;
};
var scratchOptions = {
length : undefined,
topRadius : undefined,
bottomRadius : undefined,
slices : undefined,
numberOfVerticalLines : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {CylinderOutlineGeometry} [result] The object into which to store the result.
* @returns {CylinderOutlineGeometry} The modified result parameter or a new CylinderOutlineGeometry instance if one was not provided.
*/
CylinderOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var length = array[startingIndex++];
var topRadius = array[startingIndex++];
var bottomRadius = array[startingIndex++];
var slices = array[startingIndex++];
var numberOfVerticalLines = array[startingIndex];
if (!defined(result)) {
scratchOptions.length = length;
scratchOptions.topRadius = topRadius;
scratchOptions.bottomRadius = bottomRadius;
scratchOptions.slices = slices;
scratchOptions.numberOfVerticalLines = numberOfVerticalLines;
return new CylinderOutlineGeometry(scratchOptions);
}
result._length = length;
result._topRadius = topRadius;
result._bottomRadius = bottomRadius;
result._slices = slices;
result._numberOfVerticalLines = numberOfVerticalLines;
return result;
};
/**
* Computes the geometric representation of an outline of a cylinder, including its vertices, indices, and a bounding sphere.
*
* @param {CylinderOutlineGeometry} cylinderGeometry A description of the cylinder outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
CylinderOutlineGeometry.createGeometry = function(cylinderGeometry) {
var length = cylinderGeometry._length;
var topRadius = cylinderGeometry._topRadius;
var bottomRadius = cylinderGeometry._bottomRadius;
var slices = cylinderGeometry._slices;
var numberOfVerticalLines = cylinderGeometry._numberOfVerticalLines;
if ((length <= 0) || (topRadius < 0) || (bottomRadius < 0) || ((topRadius === 0) && (bottomRadius === 0))) {
return;
}
var numVertices = slices * 2;
var positions = CylinderGeometryLibrary.computePositions(length, topRadius, bottomRadius, slices, false);
var numIndices = slices * 2;
var numSide;
if (numberOfVerticalLines > 0) {
var numSideLines = Math.min(numberOfVerticalLines, slices);
numSide = Math.round(slices / numSideLines);
numIndices += numSideLines;
}
var indices = IndexDatatype.createTypedArray(numVertices, numIndices * 2);
var index = 0;
for (var i = 0; i < slices - 1; i++) {
indices[index++] = i;
indices[index++] = i + 1;
indices[index++] = i + slices;
indices[index++] = i + 1 + slices;
}
indices[index++] = slices - 1;
indices[index++] = 0;
indices[index++] = slices + slices - 1;
indices[index++] = slices;
if (numberOfVerticalLines > 0) {
for (i = 0; i < slices; i += numSide) {
indices[index++] = i;
indices[index++] = i + slices;
}
}
var attributes = new GeometryAttributes();
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
radiusScratch.x = length * 0.5;
radiusScratch.y = Math.max(bottomRadius, topRadius);
var boundingSphere = new BoundingSphere(Cartesian3.ZERO, Cartesian2.magnitude(radiusScratch));
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : boundingSphere
});
};
return CylinderOutlineGeometry;
});
/*global define*/
define('Core/DefaultProxy',[],function() {
'use strict';
/**
* A simple proxy that appends the desired resource as the sole query parameter
* to the given proxy URL.
*
* @alias DefaultProxy
* @constructor
*
* @param {String} proxy The proxy URL that will be used to requests all resources.
*/
function DefaultProxy(proxy) {
this.proxy = proxy;
}
/**
* Get the final URL to use to request a given resource.
*
* @param {String} resource The resource to request.
* @returns {String} proxied resource
*/
DefaultProxy.prototype.getURL = function(resource) {
var prefix = this.proxy.indexOf('?') === -1 ? '?' : '';
return this.proxy + prefix + encodeURIComponent(resource);
};
return DefaultProxy;
});
/*global define*/
define('Core/DistanceDisplayCondition',[
'./defaultValue',
'./defined',
'./defineProperties'
], function(
defaultValue,
defined,
defineProperties) {
'use strict';
/**
* Determines visibility based on the distance to the camera.
*
* @alias DistanceDisplayCondition
* @constructor
*
* @param {Number} [near=0.0] The smallest distance in the interval where the object is visible.
* @param {Number} [far=Number.MAX_VALUE] The largest distance in the interval where the object is visible.
*
* @example
* // Make a billboard that is only visible when the distance to the camera is between 10 and 20 meters.
* billboard.distanceDisplayCondition = new DistanceDisplayCondition(10.0 20.0);
*/
function DistanceDisplayCondition(near, far) {
near = defaultValue(near, 0.0);
this._near = near;
far = defaultValue(far, Number.MAX_VALUE);
this._far = far;
}
defineProperties(DistanceDisplayCondition.prototype, {
/**
* The smallest distance in the interval where the object is visible.
* @memberof DistanceDisplayCondition.prototype
* @type {Number}
* @default 0.0
*/
near : {
get : function() {
return this._near;
},
set : function(value) {
this._near = value;
}
},
/**
* The largest distance in the interval where the object is visible.
* @memberof DistanceDisplayCondition.prototype
* @type {Number}
* @default Number.MAX_VALUE
*/
far : {
get : function() {
return this._far;
},
set : function(value) {
this._far = value;
}
}
});
/**
* Determines if two distance display conditions are equal.
*
* @param {DistanceDisplayCondition} left A distance display condition.
* @param {DistanceDisplayCondition} right Another distance display condition.
* @return {Boolean} Whether the two distance display conditions are equal.
*/
DistanceDisplayCondition.equals = function(left, right) {
return left === right ||
(defined(left) &&
defined(right) &&
left.near === right.near &&
left.far === right.far);
};
/**
* Duplicates a distance display condition instance.
*
* @param {DistanceDisplayCondition} [value] The distance display condition to duplicate.
* @param {DistanceDisplayCondition} [result] The result onto which to store the result.
* @return {DistanceDisplayCondition} The duplicated instance.
*/
DistanceDisplayCondition.clone = function(value, result) {
if (!defined(value)) {
return undefined;
}
if (!defined(result)) {
result = new DistanceDisplayCondition();
}
result.near = value.near;
result.far = value.far;
return result;
};
/**
* Duplicates this instance.
*
* @param {DistanceDisplayCondition} [result] The result onto which to store the result.
* @return {DistanceDisplayCondition} The duplicated instance.
*/
DistanceDisplayCondition.prototype.clone = function(result) {
return DistanceDisplayCondition.clone(this, result);
};
/**
* Determines if this distance display condition is equal to another.
*
* @param {DistanceDisplayCondition} other Another distance display condition.
* @return {Boolean} Whether this distance display condition is equal to the other.
*/
DistanceDisplayCondition.prototype.equals = function(other) {
return DistanceDisplayCondition.equals(this, other);
};
return DistanceDisplayCondition;
});
/*global define*/
define('Core/DistanceDisplayConditionGeometryInstanceAttribute',[
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError'
], function(
ComponentDatatype,
defaultValue,
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* Value and type information for per-instance geometry attribute that determines if the geometry instance has a distance display condition.
*
* @alias DistanceDisplayConditionGeometryInstanceAttribute
* @constructor
*
* @param {Number} [near=0.0] The near distance.
* @param {Number} [far=Number.MAX_VALUE] The far distance.
*
* @exception {DeveloperError} far must be greater than near.
*
* @example
* var instance = new Cesium.GeometryInstance({
* geometry : new Cesium.BoxGeometry({
* vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL,
* minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0),
* maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0)
* }),
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
* id : 'box',
* attributes : {
* show : new Cesium.DistanceDisplayConditionGeometryInstanceAttribute(100.0, 10000.0)
* }
* });
*
* @see GeometryInstance
* @see GeometryInstanceAttribute
*/
function DistanceDisplayConditionGeometryInstanceAttribute(near, far) {
near = defaultValue(near, 0.0);
far = defaultValue(far, Number.MAX_VALUE);
if (far <= near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
/**
* The values for the attributes stored in a typed array.
*
* @type Float32Array
*
* @default [0.0, 0.0, Number.MAX_VALUE]
*/
this.value = new Float32Array([near, far]);
}
defineProperties(DistanceDisplayConditionGeometryInstanceAttribute.prototype, {
/**
* The datatype of each component in the attribute, e.g., individual elements in
* {@link DistanceDisplayConditionGeometryInstanceAttribute#value}.
*
* @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype
*
* @type {ComponentDatatype}
* @readonly
*
* @default {@link ComponentDatatype.FLOAT}
*/
componentDatatype : {
get : function() {
return ComponentDatatype.FLOAT;
}
},
/**
* The number of components in the attributes, i.e., {@link DistanceDisplayConditionGeometryInstanceAttribute#value}.
*
* @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype
*
* @type {Number}
* @readonly
*
* @default 3
*/
componentsPerAttribute : {
get : function() {
return 2;
}
},
/**
* When true
and componentDatatype
is an integer format,
* indicate that the components should be mapped to the range [0, 1] (unsigned)
* or [-1, 1] (signed) when they are accessed as floating-point for rendering.
*
* @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
normalize : {
get : function() {
return false;
}
}
});
/**
* Creates a new {@link DistanceDisplayConditionGeometryInstanceAttribute} instance given the provided an enabled flag and {@link DistanceDisplayCondition}.
*
* @param {DistanceDisplayCondition} distanceDisplayCondition The distance display condition.
* @returns {DistanceDisplayConditionGeometryInstanceAttribute} The new {@link DistanceDisplayConditionGeometryInstanceAttribute} instance.
*
* @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
*
* @example
* var instance = new Cesium.GeometryInstance({
* geometry : geometry,
* attributes : {
* color : Cesium.DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition),
* }
* });
*/
DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition = function(distanceDisplayCondition) {
if (!defined(distanceDisplayCondition)) {
throw new DeveloperError('distanceDisplayCondition is required.');
}
if (distanceDisplayCondition.far <= distanceDisplayCondition.near) {
throw new DeveloperError('distanceDisplayCondition.far distance must be greater than distanceDisplayCondition.near distance.');
}
return new DistanceDisplayConditionGeometryInstanceAttribute(distanceDisplayCondition.near, distanceDisplayCondition.far);
};
/**
* Converts a distance display condition to a typed array that can be used to assign a distance display condition attribute.
*
* @param {DistanceDisplayCondition} distanceDisplayCondition The distance display condition value.
* @param {Float32Array} [result] The array to store the result in, if undefined a new instance will be created.
* @returns {Float32Array} The modified result parameter or a new instance if result was undefined.
*
* @example
* var attributes = primitive.getGeometryInstanceAttributes('an id');
* attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
*/
DistanceDisplayConditionGeometryInstanceAttribute.toValue = function(distanceDisplayCondition, result) {
if (!defined(distanceDisplayCondition)) {
throw new DeveloperError('distanceDisplayCondition is required.');
}
if (!defined(result)) {
return new Float32Array([distanceDisplayCondition.near, distanceDisplayCondition.far]);
}
result[0] = distanceDisplayCondition.near;
result[1] = distanceDisplayCondition.far;
return result;
};
return DistanceDisplayConditionGeometryInstanceAttribute;
});
/**
@license
tween.js - https://github.com/sole/tween.js
Copyright (c) 2010-2012 Tween.js authors.
Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/**
* @author sole / http://soledadpenades.com
* @author mrdoob / http://mrdoob.com
* @author Robert Eisele / http://www.xarg.org
* @author Philippe / http://philippe.elsass.me
* @author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html
* @author Paul Lewis / http://www.aerotwist.com/
* @author lechecacharro
* @author Josh Faul / http://jocafa.com/
* @author egraether / http://egraether.com/
* @author endel / http://endel.me
* @author Ben Delarre / http://delarre.net
*/
/*global define*/
define('ThirdParty/Tween',[],function() {
// Date.now shim for (ahem) Internet Explo(d|r)er
if ( Date.now === undefined ) {
Date.now = function () {
return new Date().valueOf();
};
}
var TWEEN = TWEEN || ( function () {
var _tweens = [];
return {
REVISION: '13',
getAll: function () {
return _tweens;
},
removeAll: function () {
_tweens = [];
},
add: function ( tween ) {
_tweens.push( tween );
},
remove: function ( tween ) {
var i = _tweens.indexOf( tween );
if ( i !== -1 ) {
_tweens.splice( i, 1 );
}
},
update: function ( time ) {
if ( _tweens.length === 0 ) return false;
var i = 0;
time = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
while ( i < _tweens.length ) {
if ( _tweens[ i ].update( time ) ) {
i++;
} else {
_tweens.splice( i, 1 );
}
}
return true;
}
};
} )();
TWEEN.Tween = function ( object ) {
var _object = object;
var _valuesStart = {};
var _valuesEnd = {};
var _valuesStartRepeat = {};
var _duration = 1000;
var _repeat = 0;
var _yoyo = false;
var _isPlaying = false;
var _reversed = false;
var _delayTime = 0;
var _startTime = null;
var _easingFunction = TWEEN.Easing.Linear.None;
var _interpolationFunction = TWEEN.Interpolation.Linear;
var _chainedTweens = [];
var _onStartCallback = null;
var _onStartCallbackFired = false;
var _onUpdateCallback = null;
var _onCompleteCallback = null;
var _onStopCallback = null;
// Set all starting values present on the target object
for ( var field in object ) {
_valuesStart[ field ] = parseFloat(object[field], 10);
}
this.to = function ( properties, duration ) {
if ( duration !== undefined ) {
_duration = duration;
}
_valuesEnd = properties;
return this;
};
this.start = function ( time ) {
TWEEN.add( this );
_isPlaying = true;
_onStartCallbackFired = false;
_startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
_startTime += _delayTime;
for ( var property in _valuesEnd ) {
// check if an Array was provided as property value
if ( _valuesEnd[ property ] instanceof Array ) {
if ( _valuesEnd[ property ].length === 0 ) {
continue;
}
// create a local copy of the Array with the start value at the front
_valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] );
}
_valuesStart[ property ] = _object[ property ];
if( ( _valuesStart[ property ] instanceof Array ) === false ) {
_valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings
}
_valuesStartRepeat[ property ] = _valuesStart[ property ] || 0;
}
return this;
};
this.stop = function () {
if ( !_isPlaying ) {
return this;
}
TWEEN.remove( this );
_isPlaying = false;
if ( _onStopCallback !== null ) {
_onStopCallback.call( _object );
}
this.stopChainedTweens();
return this;
};
this.stopChainedTweens = function () {
for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) {
_chainedTweens[ i ].stop();
}
};
this.delay = function ( amount ) {
_delayTime = amount;
return this;
};
this.repeat = function ( times ) {
_repeat = times;
return this;
};
this.yoyo = function( yoyo ) {
_yoyo = yoyo;
return this;
};
this.easing = function ( easing ) {
_easingFunction = easing;
return this;
};
this.interpolation = function ( interpolation ) {
_interpolationFunction = interpolation;
return this;
};
this.chain = function () {
_chainedTweens = arguments;
return this;
};
this.onStart = function ( callback ) {
_onStartCallback = callback;
return this;
};
this.onUpdate = function ( callback ) {
_onUpdateCallback = callback;
return this;
};
this.onComplete = function ( callback ) {
_onCompleteCallback = callback;
return this;
};
this.onStop = function ( callback ) {
_onStopCallback = callback;
return this;
};
this.update = function ( time ) {
var property;
if ( time < _startTime ) {
return true;
}
if ( _onStartCallbackFired === false ) {
if ( _onStartCallback !== null ) {
_onStartCallback.call( _object );
}
_onStartCallbackFired = true;
}
var elapsed = ( time - _startTime ) / _duration;
elapsed = elapsed > 1 ? 1 : elapsed;
var value = _easingFunction( elapsed );
for ( property in _valuesEnd ) {
var start = _valuesStart[ property ] || 0;
var end = _valuesEnd[ property ];
if ( end instanceof Array ) {
_object[ property ] = _interpolationFunction( end, value );
} else {
// Parses relative end values with start as base (e.g.: +10, -3)
if ( typeof(end) === "string" ) {
end = start + parseFloat(end, 10);
}
// protect against non numeric properties.
if ( typeof(end) === "number" ) {
_object[ property ] = start + ( end - start ) * value;
}
}
}
if ( _onUpdateCallback !== null ) {
_onUpdateCallback.call( _object, value );
}
if ( elapsed == 1 ) {
if ( _repeat > 0 ) {
if( isFinite( _repeat ) ) {
_repeat--;
}
// reassign starting values, restart by making startTime = now
for( property in _valuesStartRepeat ) {
if ( typeof( _valuesEnd[ property ] ) === "string" ) {
_valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10);
}
if (_yoyo) {
var tmp = _valuesStartRepeat[ property ];
_valuesStartRepeat[ property ] = _valuesEnd[ property ];
_valuesEnd[ property ] = tmp;
}
_valuesStart[ property ] = _valuesStartRepeat[ property ];
}
if (_yoyo) {
_reversed = !_reversed;
}
_startTime = time + _delayTime;
return true;
} else {
if ( _onCompleteCallback !== null ) {
_onCompleteCallback.call( _object );
}
for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) {
_chainedTweens[ i ].start( time );
}
return false;
}
}
return true;
};
};
TWEEN.Easing = {
Linear: {
None: function ( k ) {
return k;
}
},
Quadratic: {
In: function ( k ) {
return k * k;
},
Out: function ( k ) {
return k * ( 2 - k );
},
InOut: function ( k ) {
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k;
return - 0.5 * ( --k * ( k - 2 ) - 1 );
}
},
Cubic: {
In: function ( k ) {
return k * k * k;
},
Out: function ( k ) {
return --k * k * k + 1;
},
InOut: function ( k ) {
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k;
return 0.5 * ( ( k -= 2 ) * k * k + 2 );
}
},
Quartic: {
In: function ( k ) {
return k * k * k * k;
},
Out: function ( k ) {
return 1 - ( --k * k * k * k );
},
InOut: function ( k ) {
if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k;
return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 );
}
},
Quintic: {
In: function ( k ) {
return k * k * k * k * k;
},
Out: function ( k ) {
return --k * k * k * k * k + 1;
},
InOut: function ( k ) {
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k;
return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 );
}
},
Sinusoidal: {
In: function ( k ) {
return 1 - Math.cos( k * Math.PI / 2 );
},
Out: function ( k ) {
return Math.sin( k * Math.PI / 2 );
},
InOut: function ( k ) {
return 0.5 * ( 1 - Math.cos( Math.PI * k ) );
}
},
Exponential: {
In: function ( k ) {
return k === 0 ? 0 : Math.pow( 1024, k - 1 );
},
Out: function ( k ) {
return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k );
},
InOut: function ( k ) {
if ( k === 0 ) return 0;
if ( k === 1 ) return 1;
if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 );
return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 );
}
},
Circular: {
In: function ( k ) {
return 1 - Math.sqrt( 1 - k * k );
},
Out: function ( k ) {
return Math.sqrt( 1 - ( --k * k ) );
},
InOut: function ( k ) {
if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1);
return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1);
}
},
Elastic: {
In: function ( k ) {
var s, a = 0.1, p = 0.4;
if ( k === 0 ) return 0;
if ( k === 1 ) return 1;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
},
Out: function ( k ) {
var s, a = 0.1, p = 0.4;
if ( k === 0 ) return 0;
if ( k === 1 ) return 1;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
},
InOut: function ( k ) {
var s, a = 0.1, p = 0.4;
if ( k === 0 ) return 0;
if ( k === 1 ) return 1;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1;
}
},
Back: {
In: function ( k ) {
var s = 1.70158;
return k * k * ( ( s + 1 ) * k - s );
},
Out: function ( k ) {
var s = 1.70158;
return --k * k * ( ( s + 1 ) * k + s ) + 1;
},
InOut: function ( k ) {
var s = 1.70158 * 1.525;
if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) );
return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 );
}
},
Bounce: {
In: function ( k ) {
return 1 - TWEEN.Easing.Bounce.Out( 1 - k );
},
Out: function ( k ) {
if ( k < ( 1 / 2.75 ) ) {
return 7.5625 * k * k;
} else if ( k < ( 2 / 2.75 ) ) {
return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
} else if ( k < ( 2.5 / 2.75 ) ) {
return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
} else {
return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
}
},
InOut: function ( k ) {
if ( k < 0.5 ) return TWEEN.Easing.Bounce.In( k * 2 ) * 0.5;
return TWEEN.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5;
}
}
};
TWEEN.Interpolation = {
Linear: function ( v, k ) {
var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.Linear;
if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f );
if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f );
return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i );
},
Bezier: function ( v, k ) {
var b = 0, n = v.length - 1, pw = Math.pow, bn = TWEEN.Interpolation.Utils.Bernstein, i;
for ( i = 0; i <= n; i++ ) {
b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i );
}
return b;
},
CatmullRom: function ( v, k ) {
var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.CatmullRom;
if ( v[ 0 ] === v[ m ] ) {
if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) );
return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i );
} else {
if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] );
if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] );
return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i );
}
},
Utils: {
Linear: function ( p0, p1, t ) {
return ( p1 - p0 ) * t + p0;
},
Bernstein: function ( n , i ) {
var fc = TWEEN.Interpolation.Utils.Factorial;
return fc( n ) / fc( i ) / fc( n - i );
},
Factorial: ( function () {
var a = [ 1 ];
return function ( n ) {
var s = 1, i;
if ( a[ n ] ) return a[ n ];
for ( i = n; i > 1; i-- ) s *= i;
return a[ n ] = s;
};
} )(),
CatmullRom: function ( p0, p1, p2, p3, t ) {
var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2;
return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
}
}
};
return TWEEN;
});
/*global define*/
define('Core/EasingFunction',[
'../ThirdParty/Tween',
'./freezeObject'
], function(
Tween,
freezeObject) {
'use strict';
/**
* Easing functions for use with {@link TweenCollection}. These function are from
* {@link https://github.com/sole/tween.js/|Tween.js} and Robert Penner. See the
* {@link http://sole.github.io/tween.js/examples/03_graphs.html|Tween.js graphs for each function}.
*
* @exports EasingFunction
*/
var EasingFunction = {
/**
* Linear easing.
*
* @type {EasingFunction~Callback}
* @constant
*/
LINEAR_NONE : Tween.Easing.Linear.None,
/**
* Quadratic in.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUADRACTIC_IN : Tween.Easing.Quadratic.In,
/**
* Quadratic out.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUADRACTIC_OUT : Tween.Easing.Quadratic.Out,
/**
* Quadratic in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUADRACTIC_IN_OUT : Tween.Easing.Quadratic.InOut,
/**
* Cubic in.
*
* @type {EasingFunction~Callback}
* @constant
*/
CUBIC_IN : Tween.Easing.Cubic.In,
/**
* Cubic out.
*
* @type {EasingFunction~Callback}
* @constant
*/
CUBIC_OUT : Tween.Easing.Cubic.Out,
/**
* Cubic in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
CUBIC_IN_OUT : Tween.Easing.Cubic.InOut,
/**
* Quartic in.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUARTIC_IN : Tween.Easing.Quartic.In,
/**
* Quartic out.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUARTIC_OUT : Tween.Easing.Quartic.Out,
/**
* Quartic in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUARTIC_IN_OUT : Tween.Easing.Quartic.InOut,
/**
* Quintic in.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUINTIC_IN : Tween.Easing.Quintic.In,
/**
* Quintic out.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUINTIC_OUT : Tween.Easing.Quintic.Out,
/**
* Quintic in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
QUINTIC_IN_OUT : Tween.Easing.Quintic.InOut,
/**
* Sinusoidal in.
*
* @type {EasingFunction~Callback}
* @constant
*/
SINUSOIDAL_IN : Tween.Easing.Sinusoidal.In,
/**
* Sinusoidal out.
*
* @type {EasingFunction~Callback}
* @constant
*/
SINUSOIDAL_OUT : Tween.Easing.Sinusoidal.Out,
/**
* Sinusoidal in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
SINUSOIDAL_IN_OUT : Tween.Easing.Sinusoidal.InOut,
/**
* Exponential in.
*
* @type {EasingFunction~Callback}
* @constant
*/
EXPONENTIAL_IN : Tween.Easing.Exponential.In,
/**
* Exponential out.
*
* @type {EasingFunction~Callback}
* @constant
*/
EXPONENTIAL_OUT : Tween.Easing.Exponential.Out,
/**
* Exponential in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
EXPONENTIAL_IN_OUT : Tween.Easing.Exponential.InOut,
/**
* Circular in.
*
* @type {EasingFunction~Callback}
* @constant
*/
CIRCULAR_IN : Tween.Easing.Circular.In,
/**
* Circular out.
*
* @type {EasingFunction~Callback}
* @constant
*/
CIRCULAR_OUT : Tween.Easing.Circular.Out,
/**
* Circular in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
CIRCULAR_IN_OUT : Tween.Easing.Circular.InOut,
/**
* Elastic in.
*
* @type {EasingFunction~Callback}
* @constant
*/
ELASTIC_IN : Tween.Easing.Elastic.In,
/**
* Elastic out.
*
* @type {EasingFunction~Callback}
* @constant
*/
ELASTIC_OUT : Tween.Easing.Elastic.Out,
/**
* Elastic in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
ELASTIC_IN_OUT : Tween.Easing.Elastic.InOut,
/**
* Back in.
*
* @type {EasingFunction~Callback}
* @constant
*/
BACK_IN : Tween.Easing.Back.In,
/**
* Back out.
*
* @type {EasingFunction~Callback}
* @constant
*/
BACK_OUT : Tween.Easing.Back.Out,
/**
* Back in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
BACK_IN_OUT : Tween.Easing.Back.InOut,
/**
* Bounce in.
*
* @type {EasingFunction~Callback}
* @constant
*/
BOUNCE_IN : Tween.Easing.Bounce.In,
/**
* Bounce out.
*
* @type {EasingFunction~Callback}
* @constant
*/
BOUNCE_OUT : Tween.Easing.Bounce.Out,
/**
* Bounce in then out.
*
* @type {EasingFunction~Callback}
* @constant
*/
BOUNCE_IN_OUT : Tween.Easing.Bounce.InOut
};
/**
* Function interface for implementing a custom easing function.
* @callback EasingFunction~Callback
* @param {Number} time The time in the range [0, 1]
.
* @returns {Number} The value of the function at the given time.
*
* @example
* function quadraticIn(time) {
* return time * time;
* }
*
* @example
* function quadraticOut(time) {
* return time * (2.0 - time);
* }
*/
return freezeObject(EasingFunction);
});
/*global define*/
define('Core/EllipsoidGeometry',[
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PrimitiveType',
'./VertexFormat'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PrimitiveType,
VertexFormat) {
'use strict';
var scratchPosition = new Cartesian3();
var scratchNormal = new Cartesian3();
var scratchTangent = new Cartesian3();
var scratchBinormal = new Cartesian3();
var scratchNormalST = new Cartesian3();
var defaultRadii = new Cartesian3(1.0, 1.0, 1.0);
var cos = Math.cos;
var sin = Math.sin;
/**
* A description of an ellipsoid centered at the origin.
*
* @alias EllipsoidGeometry
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions.
* @param {Number} [options.stackPartitions=64] The number of times to partition the ellipsoid into stacks.
* @param {Number} [options.slicePartitions=64] The number of times to partition the ellipsoid into radial slices.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
*
* @exception {DeveloperError} options.slicePartitions cannot be less than three.
* @exception {DeveloperError} options.stackPartitions cannot be less than three.
*
* @see EllipsoidGeometry#createGeometry
*
* @example
* var ellipsoid = new Cesium.EllipsoidGeometry({
* vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
* radii : new Cesium.Cartesian3(1000000.0, 500000.0, 500000.0)
* });
* var geometry = Cesium.EllipsoidGeometry.createGeometry(ellipsoid);
*/
function EllipsoidGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var radii = defaultValue(options.radii, defaultRadii);
var stackPartitions = defaultValue(options.stackPartitions, 64);
var slicePartitions = defaultValue(options.slicePartitions, 64);
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
if (slicePartitions < 3) {
throw new DeveloperError ('options.slicePartitions cannot be less than three.');
}
if (stackPartitions < 3) {
throw new DeveloperError('options.stackPartitions cannot be less than three.');
}
this._radii = Cartesian3.clone(radii);
this._stackPartitions = stackPartitions;
this._slicePartitions = slicePartitions;
this._vertexFormat = VertexFormat.clone(vertexFormat);
this._workerName = 'createEllipsoidGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
EllipsoidGeometry.packedLength = Cartesian3.packedLength + VertexFormat.packedLength + 2;
/**
* Stores the provided instance into the provided array.
*
* @param {EllipsoidGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
EllipsoidGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._radii, array, startingIndex);
startingIndex += Cartesian3.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
array[startingIndex++] = value._stackPartitions;
array[startingIndex] = value._slicePartitions;
return array;
};
var scratchRadii = new Cartesian3();
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
radii : scratchRadii,
vertexFormat : scratchVertexFormat,
stackPartitions : undefined,
slicePartitions : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {EllipsoidGeometry} [result] The object into which to store the result.
* @returns {EllipsoidGeometry} The modified result parameter or a new EllipsoidGeometry instance if one was not provided.
*/
EllipsoidGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var radii = Cartesian3.unpack(array, startingIndex, scratchRadii);
startingIndex += Cartesian3.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var stackPartitions = array[startingIndex++];
var slicePartitions = array[startingIndex];
if (!defined(result)) {
scratchOptions.stackPartitions = stackPartitions;
scratchOptions.slicePartitions = slicePartitions;
return new EllipsoidGeometry(scratchOptions);
}
result._radii = Cartesian3.clone(radii, result._radii);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._stackPartitions = stackPartitions;
result._slicePartitions = slicePartitions;
return result;
};
/**
* Computes the geometric representation of an ellipsoid, including its vertices, indices, and a bounding sphere.
*
* @param {EllipsoidGeometry} ellipsoidGeometry A description of the ellipsoid.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
EllipsoidGeometry.createGeometry = function(ellipsoidGeometry) {
var radii = ellipsoidGeometry._radii;
if ((radii.x <= 0) || (radii.y <= 0) || (radii.z <= 0)) {
return;
}
var ellipsoid = Ellipsoid.fromCartesian3(radii);
var vertexFormat = ellipsoidGeometry._vertexFormat;
// The extra slice and stack are for duplicating points at the x axis and poles.
// We need the texture coordinates to interpolate from (2 * pi - delta) to 2 * pi instead of
// (2 * pi - delta) to 0.
var slicePartitions = ellipsoidGeometry._slicePartitions + 1;
var stackPartitions = ellipsoidGeometry._stackPartitions + 1;
var vertexCount = stackPartitions * slicePartitions;
var positions = new Float64Array(vertexCount * 3);
var numIndices = 6 * (slicePartitions - 1) * (stackPartitions - 2);
var indices = IndexDatatype.createTypedArray(vertexCount, numIndices);
var normals = (vertexFormat.normal) ? new Float32Array(vertexCount * 3) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(vertexCount * 3) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(vertexCount * 3) : undefined;
var st = (vertexFormat.st) ? new Float32Array(vertexCount * 2) : undefined;
var cosTheta = new Array(slicePartitions);
var sinTheta = new Array(slicePartitions);
var i;
var j;
var index = 0;
for (i = 0; i < slicePartitions; i++) {
var theta = CesiumMath.TWO_PI * i / (slicePartitions - 1);
cosTheta[i] = cos(theta);
sinTheta[i] = sin(theta);
// duplicate first point for correct
// texture coordinates at the north pole.
positions[index++] = 0.0;
positions[index++] = 0.0;
positions[index++] = radii.z;
}
for (i = 1; i < stackPartitions - 1; i++) {
var phi = Math.PI * i / (stackPartitions - 1);
var sinPhi = sin(phi);
var xSinPhi = radii.x * sinPhi;
var ySinPhi = radii.y * sinPhi;
var zCosPhi = radii.z * cos(phi);
for (j = 0; j < slicePartitions; j++) {
positions[index++] = cosTheta[j] * xSinPhi;
positions[index++] = sinTheta[j] * ySinPhi;
positions[index++] = zCosPhi;
}
}
for (i = 0; i < slicePartitions; i++) {
// duplicate first point for correct
// texture coordinates at the south pole.
positions[index++] = 0.0;
positions[index++] = 0.0;
positions[index++] = -radii.z;
}
var attributes = new GeometryAttributes();
if (vertexFormat.position) {
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
}
var stIndex = 0;
var normalIndex = 0;
var tangentIndex = 0;
var binormalIndex = 0;
if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
for( i = 0; i < vertexCount; i++) {
var position = Cartesian3.fromArray(positions, i * 3, scratchPosition);
var normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal);
if (vertexFormat.st) {
var normalST = Cartesian2.negate(normal, scratchNormalST);
// if the point is at or close to the pole, find a point along the same longitude
// close to the xy-plane for the s coordinate.
if (Cartesian2.magnitude(normalST) < CesiumMath.EPSILON6) {
index = (i + slicePartitions * Math.floor(stackPartitions * 0.5)) * 3;
if (index > positions.length) {
index = (i - slicePartitions * Math.floor(stackPartitions * 0.5)) * 3;
}
Cartesian3.fromArray(positions, index, normalST);
ellipsoid.geodeticSurfaceNormal(normalST, normalST);
Cartesian2.negate(normalST, normalST);
}
st[stIndex++] = (Math.atan2(normalST.y, normalST.x) / CesiumMath.TWO_PI) + 0.5;
st[stIndex++] = (Math.asin(normal.z) / Math.PI) + 0.5;
}
if (vertexFormat.normal) {
normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;
}
if (vertexFormat.tangent || vertexFormat.binormal) {
var tangent = scratchTangent;
if (i < slicePartitions || i > vertexCount - slicePartitions - 1) {
Cartesian3.cross(Cartesian3.UNIT_X, normal, tangent);
Cartesian3.normalize(tangent, tangent);
} else {
Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent);
Cartesian3.normalize(tangent, tangent);
}
if (vertexFormat.tangent) {
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
}
if (vertexFormat.binormal) {
var binormal = Cartesian3.cross(normal, tangent, scratchBinormal);
Cartesian3.normalize(binormal, binormal);
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
}
}
}
if (vertexFormat.st) {
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : st
});
}
if (vertexFormat.normal) {
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.tangent) {
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
}
index = 0;
for (j = 0; j < slicePartitions - 1; j++) {
indices[index++] = slicePartitions + j;
indices[index++] = slicePartitions + j + 1;
indices[index++] = j + 1;
}
var topOffset;
var bottomOffset;
for (i = 1; i < stackPartitions - 2; i++) {
topOffset = i * slicePartitions;
bottomOffset = (i + 1) * slicePartitions;
for (j = 0; j < slicePartitions - 1; j++) {
indices[index++] = bottomOffset + j;
indices[index++] = bottomOffset + j + 1;
indices[index++] = topOffset + j + 1;
indices[index++] = bottomOffset + j;
indices[index++] = topOffset + j + 1;
indices[index++] = topOffset + j;
}
}
i = stackPartitions - 2;
topOffset = i * slicePartitions;
bottomOffset = (i + 1) * slicePartitions;
for (j = 0; j < slicePartitions - 1; j++) {
indices[index++] = bottomOffset + j;
indices[index++] = topOffset + j + 1;
indices[index++] = topOffset + j;
}
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : BoundingSphere.fromEllipsoid(ellipsoid)
});
};
return EllipsoidGeometry;
});
/*global define*/
define('Core/EllipsoidOutlineGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PrimitiveType'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PrimitiveType) {
'use strict';
var defaultRadii = new Cartesian3(1.0, 1.0, 1.0);
var cos = Math.cos;
var sin = Math.sin;
/**
* A description of the outline of an ellipsoid centered at the origin.
*
* @alias EllipsoidOutlineGeometry
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions.
* @param {Number} [options.stackPartitions=10] The count of stacks for the ellipsoid (1 greater than the number of parallel lines).
* @param {Number} [options.slicePartitions=8] The count of slices for the ellipsoid (Equal to the number of radial lines).
* @param {Number} [options.subdivisions=128] The number of points per line, determining the granularity of the curvature.
*
* @exception {DeveloperError} options.stackPartitions must be greater than or equal to one.
* @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero.
* @exception {DeveloperError} options.subdivisions must be greater than or equal to zero.
*
* @example
* var ellipsoid = new Cesium.EllipsoidOutlineGeometry({
* radii : new Cesium.Cartesian3(1000000.0, 500000.0, 500000.0),
* stackPartitions: 6,
* slicePartitions: 5
* });
* var geometry = Cesium.EllipsoidOutlineGeometry.createGeometry(ellipsoid);
*/
function EllipsoidOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var radii = defaultValue(options.radii, defaultRadii);
var stackPartitions = defaultValue(options.stackPartitions, 10);
var slicePartitions = defaultValue(options.slicePartitions, 8);
var subdivisions = defaultValue(options.subdivisions, 128);
if (stackPartitions < 1) {
throw new DeveloperError('options.stackPartitions cannot be less than 1');
}
if (slicePartitions < 0) {
throw new DeveloperError('options.slicePartitions cannot be less than 0');
}
if (subdivisions < 0) {
throw new DeveloperError('options.subdivisions must be greater than or equal to zero.');
}
this._radii = Cartesian3.clone(radii);
this._stackPartitions = stackPartitions;
this._slicePartitions = slicePartitions;
this._subdivisions = subdivisions;
this._workerName = 'createEllipsoidOutlineGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
EllipsoidOutlineGeometry.packedLength = Cartesian3.packedLength + 3;
/**
* Stores the provided instance into the provided array.
*
* @param {EllipsoidOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
EllipsoidOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Cartesian3.pack(value._radii, array, startingIndex);
startingIndex += Cartesian3.packedLength;
array[startingIndex++] = value._stackPartitions;
array[startingIndex++] = value._slicePartitions;
array[startingIndex] = value._subdivisions;
return array;
};
var scratchRadii = new Cartesian3();
var scratchOptions = {
radii : scratchRadii,
stackPartitions : undefined,
slicePartitions : undefined,
subdivisions : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {EllipsoidOutlineGeometry} [result] The object into which to store the result.
* @returns {EllipsoidOutlineGeometry} The modified result parameter or a new EllipsoidOutlineGeometry instance if one was not provided.
*/
EllipsoidOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var radii = Cartesian3.unpack(array, startingIndex, scratchRadii);
startingIndex += Cartesian3.packedLength;
var stackPartitions = array[startingIndex++];
var slicePartitions = array[startingIndex++];
var subdivisions = array[startingIndex++];
if (!defined(result)) {
scratchOptions.stackPartitions = stackPartitions;
scratchOptions.slicePartitions = slicePartitions;
scratchOptions.subdivisions = subdivisions;
return new EllipsoidOutlineGeometry(scratchOptions);
}
result._radii = Cartesian3.clone(radii, result._radii);
result._stackPartitions = stackPartitions;
result._slicePartitions = slicePartitions;
result._subdivisions = subdivisions;
return result;
};
/**
* Computes the geometric representation of an outline of an ellipsoid, including its vertices, indices, and a bounding sphere.
*
* @param {EllipsoidOutlineGeometry} ellipsoidGeometry A description of the ellipsoid outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
EllipsoidOutlineGeometry.createGeometry = function(ellipsoidGeometry) {
var radii = ellipsoidGeometry._radii;
if ((radii.x <= 0) || (radii.y <= 0) || (radii.z <= 0)) {
return;
}
var ellipsoid = Ellipsoid.fromCartesian3(radii);
var stackPartitions = ellipsoidGeometry._stackPartitions;
var slicePartitions = ellipsoidGeometry._slicePartitions;
var subdivisions = ellipsoidGeometry._subdivisions;
var indicesSize = subdivisions * (stackPartitions + slicePartitions - 1);
var positionSize = indicesSize - slicePartitions + 2;
var positions = new Float64Array(positionSize * 3);
var indices = IndexDatatype.createTypedArray(positionSize, indicesSize * 2);
var i;
var j;
var theta;
var phi;
var cosPhi;
var sinPhi;
var index = 0;
var cosTheta = new Array(subdivisions);
var sinTheta = new Array(subdivisions);
for (i = 0; i < subdivisions; i++) {
theta = CesiumMath.TWO_PI * i / subdivisions;
cosTheta[i] = cos(theta);
sinTheta[i] = sin(theta);
}
for (i = 1; i < stackPartitions; i++) {
phi = Math.PI * i / stackPartitions;
cosPhi = cos(phi);
sinPhi = sin(phi);
for (j = 0; j < subdivisions; j++) {
positions[index++] = radii.x * cosTheta[j] * sinPhi;
positions[index++] = radii.y * sinTheta[j] * sinPhi;
positions[index++] = radii.z * cosPhi;
}
}
cosTheta.length = slicePartitions;
sinTheta.length = slicePartitions;
for (i = 0; i < slicePartitions; i++) {
theta = CesiumMath.TWO_PI * i / slicePartitions;
cosTheta[i] = cos(theta);
sinTheta[i] = sin(theta);
}
positions[index++] = 0;
positions[index++] = 0;
positions[index++] = radii.z;
for (i = 1; i < subdivisions; i++) {
phi = Math.PI * i / subdivisions;
cosPhi = cos(phi);
sinPhi = sin(phi);
for (j = 0; j < slicePartitions; j++) {
positions[index++] = radii.x * cosTheta[j] * sinPhi;
positions[index++] = radii.y * sinTheta[j] * sinPhi;
positions[index++] = radii.z * cosPhi;
}
}
positions[index++] = 0;
positions[index++] = 0;
positions[index++] = -radii.z;
index = 0;
for (i = 0; i < stackPartitions - 1; ++i) {
var topRowOffset = (i * subdivisions);
for (j = 0; j < subdivisions - 1; ++j) {
indices[index++] = topRowOffset + j;
indices[index++] = topRowOffset + j + 1;
}
indices[index++] = topRowOffset + subdivisions - 1;
indices[index++] = topRowOffset;
}
var sliceOffset = subdivisions * (stackPartitions - 1);
for (j = 1; j < slicePartitions + 1; ++j) {
indices[index++] = sliceOffset;
indices[index++] = sliceOffset + j;
}
for (i = 0; i < subdivisions - 2; ++i) {
var topOffset = (i * slicePartitions) + 1 + sliceOffset;
var bottomOffset = ((i + 1) * slicePartitions) + 1 + sliceOffset;
for (j = 0; j < slicePartitions - 1; ++j) {
indices[index++] = bottomOffset + j;
indices[index++] = topOffset + j;
}
indices[index++] = bottomOffset + slicePartitions - 1;
indices[index++] = topOffset + slicePartitions - 1;
}
var lastPosition = positions.length / 3 - 1;
for (j = lastPosition - 1; j > lastPosition - slicePartitions - 1; --j) {
indices[index++] = lastPosition;
indices[index++] = j;
}
var attributes = new GeometryAttributes({
position: new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
})
});
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : BoundingSphere.fromEllipsoid(ellipsoid)
});
};
return EllipsoidOutlineGeometry;
});
/*global define*/
define('Core/EllipsoidTerrainProvider',[
'../ThirdParty/when',
'./defaultValue',
'./defined',
'./defineProperties',
'./Ellipsoid',
'./Event',
'./GeographicTilingScheme',
'./HeightmapTerrainData',
'./TerrainProvider'
], function(
when,
defaultValue,
defined,
defineProperties,
Ellipsoid,
Event,
GeographicTilingScheme,
HeightmapTerrainData,
TerrainProvider) {
'use strict';
/**
* A very simple {@link TerrainProvider} that produces geometry by tessellating an ellipsoidal
* surface.
*
* @alias EllipsoidTerrainProvider
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {TilingScheme} [options.tilingScheme] The tiling scheme specifying how the ellipsoidal
* surface is broken into tiles. If this parameter is not provided, a {@link GeographicTilingScheme}
* is used.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
* parameter is specified, the WGS84 ellipsoid is used.
*
* @see TerrainProvider
*/
function EllipsoidTerrainProvider(options) {
options = defaultValue(options, {});
this._tilingScheme = options.tilingScheme;
if (!defined(this._tilingScheme)) {
this._tilingScheme = new GeographicTilingScheme({
ellipsoid : defaultValue(options.ellipsoid, Ellipsoid.WGS84)
});
}
// Note: the 64 below does NOT need to match the actual vertex dimensions, because
// the ellipsoid is significantly smoother than actual terrain.
this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, 64, this._tilingScheme.getNumberOfXTilesAtLevel(0));
this._errorEvent = new Event();
this._readyPromise = when.resolve(true);
}
defineProperties(EllipsoidTerrainProvider.prototype, {
/**
* Gets an event that is raised when the terrain provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof EllipsoidTerrainProvider.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets the credit to display when this terrain provider is active. Typically this is used to credit
* the source of the terrain. This function should not be called before {@link EllipsoidTerrainProvider#ready} returns true.
* @memberof EllipsoidTerrainProvider.prototype
* @type {Credit}
*/
credit : {
get : function() {
return undefined;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link EllipsoidTerrainProvider#ready} returns true.
* @memberof EllipsoidTerrainProvider.prototype
* @type {GeographicTilingScheme}
*/
tilingScheme : {
get : function() {
return this._tilingScheme;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof EllipsoidTerrainProvider.prototype
* @type {Boolean}
*/
ready : {
get : function() {
return true;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof EllipsoidTerrainProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise;
}
},
/**
* Gets a value indicating whether or not the provider includes a water mask. The water mask
* indicates which areas of the globe are water rather than land, so they can be rendered
* as a reflective surface with animated waves. This function should not be
* called before {@link EllipsoidTerrainProvider#ready} returns true.
* @memberof EllipsoidTerrainProvider.prototype
* @type {Boolean}
*/
hasWaterMask : {
get : function() {
return false;
}
},
/**
* Gets a value indicating whether or not the requested tiles include vertex normals.
* This function should not be called before {@link EllipsoidTerrainProvider#ready} returns true.
* @memberof EllipsoidTerrainProvider.prototype
* @type {Boolean}
*/
hasVertexNormals : {
get : function() {
return false;
}
}
});
/**
* Requests the geometry for a given tile. This function should not be called before
* {@link TerrainProvider#ready} returns true. The result includes terrain
* data and indicates that all child tiles are available.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
* or false if the request should be initiated regardless of the number of requests
* already in progress.
* @returns {Promise.|undefined} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
var width = 16;
var height = 16;
return new HeightmapTerrainData({
buffer : new Uint8Array(width * height),
width : width,
height : height
});
};
/**
* Gets the maximum geometric error allowed in a tile at a given level.
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error.
*/
EllipsoidTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
return this._levelZeroMaximumGeometricError / (1 << level);
};
/**
* Determines whether data for a tile is available to be loaded.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @returns {Boolean} Undefined if not supported, otherwise true or false.
*/
EllipsoidTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
return undefined;
};
return EllipsoidTerrainProvider;
});
/*global define*/
define('Core/EventHelper',[
'./defined',
'./DeveloperError'
], function(
defined,
DeveloperError) {
'use strict';
/**
* A convenience object that simplifies the common pattern of attaching event listeners
* to several events, then removing all those listeners at once later, for example, in
* a destroy method.
*
* @alias EventHelper
* @constructor
*
*
* @example
* var helper = new Cesium.EventHelper();
*
* helper.add(someObject.event, listener1, this);
* helper.add(otherObject.event, listener2, this);
*
* // later...
* helper.removeAll();
*
* @see Event
*/
function EventHelper() {
this._removalFunctions = [];
}
/**
* Adds a listener to an event, and records the registration to be cleaned up later.
*
* @param {Event} event The event to attach to.
* @param {Function} listener The function to be executed when the event is raised.
* @param {Object} [scope] An optional object scope to serve as the this
* pointer in which the listener function will execute.
* @returns {EventHelper~RemoveCallback} A function that will remove this event listener when invoked.
*
* @see Event#addEventListener
*/
EventHelper.prototype.add = function(event, listener, scope) {
if (!defined(event)) {
throw new DeveloperError('event is required');
}
var removalFunction = event.addEventListener(listener, scope);
this._removalFunctions.push(removalFunction);
var that = this;
return function() {
removalFunction();
var removalFunctions = that._removalFunctions;
removalFunctions.splice(removalFunctions.indexOf(removalFunction), 1);
};
};
/**
* Unregisters all previously added listeners.
*
* @see Event#removeEventListener
*/
EventHelper.prototype.removeAll = function() {
var removalFunctions = this._removalFunctions;
for (var i = 0, len = removalFunctions.length; i < len; ++i) {
removalFunctions[i]();
}
removalFunctions.length = 0;
};
/**
* A function that removes a listener.
* @callback EventHelper~RemoveCallback
*/
return EventHelper;
});
/*global define*/
define('Core/ExtrapolationType',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Constants to determine how an interpolated value is extrapolated
* when querying outside the bounds of available data.
*
* @exports ExtrapolationType
*
* @see SampledProperty
*/
var ExtrapolationType = {
/**
* No extrapolation occurs.
*
* @type {Number}
* @constant
*/
NONE : 0,
/**
* The first or last value is used when outside the range of sample data.
*
* @type {Number}
* @constant
*/
HOLD : 1,
/**
* The value is extrapolated.
*
* @type {Number}
* @constant
*/
EXTRAPOLATE : 2
};
return freezeObject(ExtrapolationType);
});
/*global define*/
define('Core/GeometryInstanceAttribute',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Values and type information for per-instance geometry attributes.
*
* @alias GeometryInstanceAttribute
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {ComponentDatatype} [options.componentDatatype] The datatype of each component in the attribute, e.g., individual elements in values.
* @param {Number} [options.componentsPerAttribute] A number between 1 and 4 that defines the number of components in an attributes.
* @param {Boolean} [options.normalize=false] When true
and componentDatatype
is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering.
* @param {Number[]} [options.value] The value for the attribute.
*
* @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4.
*
*
* @example
* var instance = new Cesium.GeometryInstance({
* geometry : Cesium.BoxGeometry.fromDimensions({
* dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 500000.0)
* }),
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(0.0, 0.0)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
* id : 'box',
* attributes : {
* color : new Cesium.GeometryInstanceAttribute({
* componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE,
* componentsPerAttribute : 4,
* normalize : true,
* value : [255, 255, 0, 255]
* })
* }
* });
*
* @see ColorGeometryInstanceAttribute
* @see ShowGeometryInstanceAttribute
* @see DistanceDisplayConditionGeometryInstanceAttribute
*/
function GeometryInstanceAttribute(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.componentDatatype)) {
throw new DeveloperError('options.componentDatatype is required.');
}
if (!defined(options.componentsPerAttribute)) {
throw new DeveloperError('options.componentsPerAttribute is required.');
}
if (options.componentsPerAttribute < 1 || options.componentsPerAttribute > 4) {
throw new DeveloperError('options.componentsPerAttribute must be between 1 and 4.');
}
if (!defined(options.value)) {
throw new DeveloperError('options.value is required.');
}
/**
* The datatype of each component in the attribute, e.g., individual elements in
* {@link GeometryInstanceAttribute#value}.
*
* @type ComponentDatatype
*
* @default undefined
*/
this.componentDatatype = options.componentDatatype;
/**
* A number between 1 and 4 that defines the number of components in an attributes.
* For example, a position attribute with x, y, and z components would have 3 as
* shown in the code example.
*
* @type Number
*
* @default undefined
*
* @example
* show : new Cesium.GeometryInstanceAttribute({
* componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE,
* componentsPerAttribute : 1,
* normalize : true,
* value : [1.0]
* })
*/
this.componentsPerAttribute = options.componentsPerAttribute;
/**
* When true
and componentDatatype
is an integer format,
* indicate that the components should be mapped to the range [0, 1] (unsigned)
* or [-1, 1] (signed) when they are accessed as floating-point for rendering.
*
* This is commonly used when storing colors using {@link ComponentDatatype.UNSIGNED_BYTE}.
*
*
* @type Boolean
*
* @default false
*
* @example
* attribute.componentDatatype = Cesium.ComponentDatatype.UNSIGNED_BYTE;
* attribute.componentsPerAttribute = 4;
* attribute.normalize = true;
* attribute.value = [
* Cesium.Color.floatToByte(color.red),
* Cesium.Color.floatToByte(color.green),
* Cesium.Color.floatToByte(color.blue),
* Cesium.Color.floatToByte(color.alpha)
* ];
*/
this.normalize = defaultValue(options.normalize, false);
/**
* The values for the attributes stored in a typed array. In the code example,
* every three elements in values
defines one attributes since
* componentsPerAttribute
is 3.
*
* @type {Number[]}
*
* @default undefined
*
* @example
* show : new Cesium.GeometryInstanceAttribute({
* componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE,
* componentsPerAttribute : 1,
* normalize : true,
* value : [1.0]
* })
*/
this.value = options.value;
}
return GeometryInstanceAttribute;
});
/*global define*/
define('Core/getBaseUri',[
'../ThirdParty/Uri',
'./defined',
'./DeveloperError'
], function(
Uri,
defined,
DeveloperError) {
'use strict';
/**
* Given a URI, returns the base path of the URI.
* @exports getBaseUri
*
* @param {String} uri The Uri.
* @param {Boolean} [includeQuery = false] Whether or not to include the query string and fragment form the uri
* @returns {String} The base path of the Uri.
*
* @example
* // basePath will be "/Gallery/";
* var basePath = Cesium.getBaseUri('/Gallery/simple.czml?value=true&example=false');
*
* // basePath will be "/Gallery/?value=true&example=false";
* var basePath = Cesium.getBaseUri('/Gallery/simple.czml?value=true&example=false', true);
*/
function getBaseUri(uri, includeQuery) {
if (!defined(uri)) {
throw new DeveloperError('uri is required.');
}
var basePath = '';
var i = uri.lastIndexOf('/');
if (i !== -1) {
basePath = uri.substring(0, i + 1);
}
if (!includeQuery) {
return basePath;
}
uri = new Uri(uri);
if (defined(uri.query)) {
basePath += '?' + uri.query;
}
if (defined(uri.fragment)){
basePath += '#' + uri.fragment;
}
return basePath;
}
return getBaseUri;
});
/*global define*/
define('Core/getExtensionFromUri',[
'../ThirdParty/Uri',
'./defined',
'./DeveloperError'
], function(
Uri,
defined,
DeveloperError) {
'use strict';
/**
* Given a URI, returns the extension of the URI.
* @exports getExtensionFromUri
*
* @param {String} uri The Uri.
* @returns {String} The extension of the Uri.
*
* @example
* //extension will be "czml";
* var extension = Cesium.getExtensionFromUri('/Gallery/simple.czml?value=true&example=false');
*/
function getExtensionFromUri(uri) {
if (!defined(uri)) {
throw new DeveloperError('uri is required.');
}
var uriObject = new Uri(uri);
uriObject.normalize();
var path = uriObject.path;
var index = path.lastIndexOf('/');
if (index !== -1) {
path = path.substr(index + 1);
}
index = path.lastIndexOf('.');
if (index === -1) {
path = '';
} else {
path = path.substr(index + 1);
}
return path;
}
return getExtensionFromUri;
});
/*global define*/
define('Core/getFilenameFromUri',[
'../ThirdParty/Uri',
'./defined',
'./DeveloperError'
], function(
Uri,
defined,
DeveloperError) {
'use strict';
/**
* Given a URI, returns the last segment of the URI, removing any path or query information.
* @exports getFilenameFromUri
*
* @param {String} uri The Uri.
* @returns {String} The last segment of the Uri.
*
* @example
* //fileName will be"simple.czml";
* var fileName = Cesium.getFilenameFromUri('/Gallery/simple.czml?value=true&example=false');
*/
function getFilenameFromUri(uri) {
if (!defined(uri)) {
throw new DeveloperError('uri is required.');
}
var uriObject = new Uri(uri);
uriObject.normalize();
var path = uriObject.path;
var index = path.lastIndexOf('/');
if (index !== -1) {
path = path.substr(index + 1);
}
return path;
}
return getFilenameFromUri;
});
/*global define*/
define('Core/getStringFromTypedArray',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/*global TextDecoder*/
/**
* @private
*/
function getStringFromTypedArray(uint8Array, byteOffset, byteLength) {
if (!defined(uint8Array)) {
throw new DeveloperError('uint8Array is required.');
}
if (byteOffset < 0) {
throw new DeveloperError('byteOffset cannot be negative.');
}
if (byteLength < 0) {
throw new DeveloperError('byteLength cannot be negative.');
}
if ((byteOffset + byteLength) > uint8Array.byteLength) {
throw new DeveloperError('sub-region exceeds array bounds.');
}
byteOffset = defaultValue(byteOffset, 0);
byteLength = defaultValue(byteLength, uint8Array.byteLength - byteOffset);
uint8Array = uint8Array.subarray(byteOffset, byteOffset + byteLength);
return getStringFromTypedArray.decode(uint8Array);
}
// Exposed functions for testing
getStringFromTypedArray.decodeWithTextDecoder = function(view) {
var decoder = new TextDecoder('utf-8');
return decoder.decode(view);
};
getStringFromTypedArray.decodeWithFromCharCode = function(view) {
var result = '';
var length = view.length;
// Convert one character at a time to avoid stack overflow on iPad.
//
// fromCharCode will not handle all legal Unicode values (up to 21 bits). See
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode
for (var i = 0; i < length; ++i) {
result += String.fromCharCode(view[i]);
}
return result;
};
if (typeof TextDecoder !== 'undefined') {
getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithTextDecoder;
} else {
getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithFromCharCode;
}
return getStringFromTypedArray;
});
/*global define*/
define('Core/getMagic',[
'./defaultValue',
'./getStringFromTypedArray'
], function(
defaultValue,
getStringFromTypedArray) {
'use strict';
/**
* @private
*/
function getMagic(uint8Array, byteOffset) {
byteOffset = defaultValue(byteOffset, 0);
return getStringFromTypedArray(uint8Array, byteOffset, Math.min(4, uint8Array.length));
}
return getMagic;
});
/*global define*/
define('Core/HeadingPitchRange',[
'./defaultValue',
'./defined'
], function(
defaultValue,
defined) {
'use strict';
/**
* Defines a heading angle, pitch angle, and range in a local frame.
* Heading is the rotation from the local north direction where a positive angle is increasing eastward.
* Pitch is the rotation from the local xy-plane. Positive pitch angles are above the plane. Negative pitch
* angles are below the plane. Range is the distance from the center of the frame.
* @alias HeadingPitchRange
* @constructor
*
* @param {Number} [heading=0.0] The heading angle in radians.
* @param {Number} [pitch=0.0] The pitch angle in radians.
* @param {Number} [range=0.0] The distance from the center in meters.
*/
function HeadingPitchRange(heading, pitch, range) {
/**
* Heading is the rotation from the local north direction where a positive angle is increasing eastward.
* @type {Number}
*/
this.heading = defaultValue(heading, 0.0);
/**
* Pitch is the rotation from the local xy-plane. Positive pitch angles
* are above the plane. Negative pitch angles are below the plane.
* @type {Number}
*/
this.pitch = defaultValue(pitch, 0.0);
/**
* Range is the distance from the center of the local frame.
* @type {Number}
*/
this.range = defaultValue(range, 0.0);
}
/**
* Duplicates a HeadingPitchRange instance.
*
* @param {HeadingPitchRange} hpr The HeadingPitchRange to duplicate.
* @param {HeadingPitchRange} [result] The object onto which to store the result.
* @returns {HeadingPitchRange} The modified result parameter or a new HeadingPitchRange instance if one was not provided. (Returns undefined if hpr is undefined)
*/
HeadingPitchRange.clone = function(hpr, result) {
if (!defined(hpr)) {
return undefined;
}
if (!defined(result)) {
result = new HeadingPitchRange();
}
result.heading = hpr.heading;
result.pitch = hpr.pitch;
result.range = hpr.range;
return result;
};
return HeadingPitchRange;
});
/*global define*/
define('Core/HermitePolynomialApproximation',[
'./defaultValue',
'./defined',
'./DeveloperError',
'./Math'
], function(
defaultValue,
defined,
DeveloperError,
CesiumMath) {
'use strict';
var factorial = CesiumMath.factorial;
function calculateCoefficientTerm(x, zIndices, xTable, derivOrder, termOrder, reservedIndices) {
var result = 0;
var reserved;
var i;
var j;
if (derivOrder > 0) {
for (i = 0; i < termOrder; i++) {
reserved = false;
for (j = 0; j < reservedIndices.length && !reserved; j++) {
if (i === reservedIndices[j]) {
reserved = true;
}
}
if (!reserved) {
reservedIndices.push(i);
result += calculateCoefficientTerm(x, zIndices, xTable, derivOrder - 1, termOrder, reservedIndices);
reservedIndices.splice(reservedIndices.length - 1, 1);
}
}
return result;
}
result = 1;
for (i = 0; i < termOrder; i++) {
reserved = false;
for (j = 0; j < reservedIndices.length && !reserved; j++) {
if (i === reservedIndices[j]) {
reserved = true;
}
}
if (!reserved) {
result *= x - xTable[zIndices[i]];
}
}
return result;
}
/**
* An {@link InterpolationAlgorithm} for performing Hermite interpolation.
*
* @exports HermitePolynomialApproximation
*/
var HermitePolynomialApproximation = {
type : 'Hermite'
};
/**
* Given the desired degree, returns the number of data points required for interpolation.
*
* @param {Number} degree The desired degree of interpolation.
* @param {Number} [inputOrder=0] The order of the inputs (0 means just the data, 1 means the data and its derivative, etc).
* @returns {Number} The number of required data points needed for the desired degree of interpolation.
* @exception {DeveloperError} degree must be 0 or greater.
* @exception {DeveloperError} inputOrder must be 0 or greater.
*/
HermitePolynomialApproximation.getRequiredDataPoints = function(degree, inputOrder) {
inputOrder = defaultValue(inputOrder, 0);
if (!defined(degree)) {
throw new DeveloperError('degree is required.');
}
if (degree < 0) {
throw new DeveloperError('degree must be 0 or greater.');
}
if (inputOrder < 0) {
throw new DeveloperError('inputOrder must be 0 or greater.');
}
return Math.max(Math.floor((degree + 1) / (inputOrder + 1)), 2);
};
/**
* Interpolates values using Hermite Polynomial Approximation.
*
* @param {Number} x The independent variable for which the dependent variables will be interpolated.
* @param {Number[]} xTable The array of independent variables to use to interpolate. The values
* in this array must be in increasing order and the same value must not occur twice in the array.
* @param {Number[]} yTable The array of dependent variables to use to interpolate. For a set of three
* dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
* @param {Number} yStride The number of dependent variable values in yTable corresponding to
* each independent variable value in xTable.
* @param {Number[]} [result] An existing array into which to store the result.
* @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
*/
HermitePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
if (!defined(result)) {
result = new Array(yStride);
}
var i;
var j;
var d;
var s;
var len;
var index;
var length = xTable.length;
var coefficients = new Array(yStride);
for (i = 0; i < yStride; i++) {
result[i] = 0;
var l = new Array(length);
coefficients[i] = l;
for (j = 0; j < length; j++) {
l[j] = [];
}
}
var zIndicesLength = length, zIndices = new Array(zIndicesLength);
for (i = 0; i < zIndicesLength; i++) {
zIndices[i] = i;
}
var highestNonZeroCoef = length - 1;
for (s = 0; s < yStride; s++) {
for (j = 0; j < zIndicesLength; j++) {
index = zIndices[j] * yStride + s;
coefficients[s][0].push(yTable[index]);
}
for (i = 1; i < zIndicesLength; i++) {
var nonZeroCoefficients = false;
for (j = 0; j < zIndicesLength - i; j++) {
var zj = xTable[zIndices[j]];
var zn = xTable[zIndices[j + i]];
var numerator;
if (zn - zj <= 0) {
index = zIndices[j] * yStride + yStride * i + s;
numerator = yTable[index];
coefficients[s][i].push(numerator / factorial(i));
} else {
numerator = (coefficients[s][i - 1][j + 1] - coefficients[s][i - 1][j]);
coefficients[s][i].push(numerator / (zn - zj));
}
nonZeroCoefficients = nonZeroCoefficients || (numerator !== 0);
}
if (!nonZeroCoefficients) {
highestNonZeroCoef = i - 1;
}
}
}
for (d = 0, len = 0; d <= len; d++) {
for (i = d; i <= highestNonZeroCoef; i++) {
var tempTerm = calculateCoefficientTerm(x, zIndices, xTable, d, i, []);
for (s = 0; s < yStride; s++) {
var coeff = coefficients[s][i][0];
result[s + d * yStride] += coeff * tempTerm;
}
}
}
return result;
};
var arrayScratch = [];
/**
* Interpolates values using Hermite Polynomial Approximation.
*
* @param {Number} x The independent variable for which the dependent variables will be interpolated.
* @param {Number[]} xTable The array of independent variables to use to interpolate. The values
* in this array must be in increasing order and the same value must not occur twice in the array.
* @param {Number[]} yTable The array of dependent variables to use to interpolate. For a set of three
* dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
* @param {Number} yStride The number of dependent variable values in yTable corresponding to
* each independent variable value in xTable.
* @param {Number} inputOrder The number of derivatives supplied for input.
* @param {Number} outputOrder The number of derivatives desired for output.
* @param {Number[]} [result] An existing array into which to store the result.
*
* @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
*/
HermitePolynomialApproximation.interpolate = function(x, xTable, yTable, yStride, inputOrder, outputOrder, result) {
var resultLength = yStride * (outputOrder + 1);
if (!defined(result)) {
result = new Array(resultLength);
}
for (var r = 0; r < resultLength; r++) {
result[r] = 0;
}
var length = xTable.length;
// The zIndices array holds copies of the addresses of the xTable values
// in the range we're looking at. Even though this just holds information already
// available in xTable this is a much more convenient format.
var zIndices = new Array(length * (inputOrder + 1));
for (var i = 0; i < length; i++) {
for (var j = 0; j < (inputOrder + 1); j++) {
zIndices[i * (inputOrder + 1) + j] = i;
}
}
var zIndiceslength = zIndices.length;
var coefficients = arrayScratch;
var highestNonZeroCoef = fillCoefficientList(coefficients, zIndices, xTable, yTable, yStride, inputOrder);
var reservedIndices = [];
var tmp = zIndiceslength * (zIndiceslength + 1) / 2;
var loopStop = Math.min(highestNonZeroCoef, outputOrder);
for (var d = 0; d <= loopStop; d++) {
for (i = d; i <= highestNonZeroCoef; i++) {
reservedIndices.length = 0;
var tempTerm = calculateCoefficientTerm(x, zIndices, xTable, d, i, reservedIndices);
var dimTwo = Math.floor(i * (1 - i) / 2) + (zIndiceslength * i);
for (var s = 0; s < yStride; s++) {
var dimOne = Math.floor(s * tmp);
var coef = coefficients[dimOne + dimTwo];
result[s + d * yStride] += coef * tempTerm;
}
}
}
return result;
};
function fillCoefficientList(coefficients, zIndices, xTable, yTable, yStride, inputOrder) {
var j;
var index;
var highestNonZero = -1;
var zIndiceslength = zIndices.length;
var tmp = zIndiceslength * (zIndiceslength + 1) / 2;
for (var s = 0; s < yStride; s++) {
var dimOne = Math.floor(s * tmp);
for (j = 0; j < zIndiceslength; j++) {
index = zIndices[j] * yStride * (inputOrder + 1) + s;
coefficients[dimOne + j] = yTable[index];
}
for (var i = 1; i < zIndiceslength; i++) {
var coefIndex = 0;
var dimTwo = Math.floor(i * (1 - i) / 2) + (zIndiceslength * i);
var nonZeroCoefficients = false;
for (j = 0; j < zIndiceslength - i; j++) {
var zj = xTable[zIndices[j]];
var zn = xTable[zIndices[j + i]];
var numerator;
var coefficient;
if (zn - zj <= 0) {
index = zIndices[j] * yStride * (inputOrder + 1) + yStride * i + s;
numerator = yTable[index];
coefficient = (numerator / CesiumMath.factorial(i));
coefficients[dimOne + dimTwo + coefIndex] = coefficient;
coefIndex++;
} else {
var dimTwoMinusOne = Math.floor((i - 1) * (2 - i) / 2) + (zIndiceslength * (i - 1));
numerator = coefficients[dimOne + dimTwoMinusOne + j + 1] - coefficients[dimOne + dimTwoMinusOne + j];
coefficient = (numerator / (zn - zj));
coefficients[dimOne + dimTwo + coefIndex] = coefficient;
coefIndex++;
}
nonZeroCoefficients = nonZeroCoefficients || (numerator !== 0.0);
}
if (nonZeroCoefficients) {
highestNonZero = Math.max(highestNonZero, i);
}
}
}
return highestNonZero;
}
return HermitePolynomialApproximation;
});
/*global define*/
define('Core/IauOrientationParameters',[],function() {
'use strict';
/**
* A structure containing the orientation data computed at a particular time. The data
* represents the direction of the pole of rotation and the rotation about that pole.
*
* These parameters correspond to the parameters in the Report from the IAU/IAG Working Group
* except that they are expressed in radians.
*
*
* @exports IauOrientationParameters
*
* @private
*/
function IauOrientationParameters(rightAscension, declination, rotation, rotationRate) {
/**
* The right ascension of the north pole of the body with respect to
* the International Celestial Reference Frame, in radians.
* @type {Number}
*
* @private
*/
this.rightAscension = rightAscension;
/**
* The declination of the north pole of the body with respect to
* the International Celestial Reference Frame, in radians.
* @type {Number}
*
* @private
*/
this.declination = declination;
/**
* The rotation about the north pole used to align a set of axes with
* the meridian defined by the IAU report, in radians.
* @type {Number}
*
* @private
*/
this.rotation = rotation;
/**
* The instantaneous rotation rate about the north pole, in radians per second.
* @type {Number}
*
* @private
*/
this.rotationRate = rotationRate;
}
return IauOrientationParameters;
});
/*global define*/
define('Core/Iau2000Orientation',[
'./defined',
'./IauOrientationParameters',
'./JulianDate',
'./Math',
'./TimeConstants'
], function(
defined,
IauOrientationParameters,
JulianDate,
CesiumMath,
TimeConstants) {
'use strict';
/**
* This is a collection of the orientation information available for central bodies.
* The data comes from the Report of the IAU/IAG Working Group on Cartographic
* Coordinates and Rotational Elements: 2000.
*
* @exports Iau2000Orientation
*
* @private
*/
var Iau2000Orientation = {};
var TdtMinusTai = 32.184;
var J2000d = 2451545.0;
var c1 = -0.0529921;
var c2 = -0.1059842;
var c3 = 13.0120009;
var c4 = 13.3407154;
var c5 = 0.9856003;
var c6 = 26.4057084;
var c7 = 13.0649930;
var c8 = 0.3287146;
var c9 = 1.7484877;
var c10 = -0.1589763;
var c11 = 0.0036096;
var c12 = 0.1643573;
var c13 = 12.9590088;
var dateTT = new JulianDate();
/**
* Compute the orientation parameters for the Moon.
*
* @param {JulianDate} [date=JulianDate.now()] The date to evaluate the parameters.
* @param {IauOrientationParameters} [result] The object onto which to store the result.
* @returns {IauOrientationParameters} The modified result parameter or a new instance representing the orientation of the Earth's Moon.
*/
Iau2000Orientation.ComputeMoon = function(date, result) {
if (!defined(date)) {
date = JulianDate.now();
}
dateTT = JulianDate.addSeconds(date, TdtMinusTai, dateTT);
var d = JulianDate.totalDays(dateTT) - J2000d;
var T = d / TimeConstants.DAYS_PER_JULIAN_CENTURY;
var E1 = (125.045 + c1 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E2 = (250.089 + c2 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E3 = (260.008 + c3 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E4 = (176.625 + c4 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E5 = (357.529 + c5 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E6 = (311.589 + c6 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E7 = (134.963 + c7 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E8 = (276.617 + c8 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E9 = (34.226 + c9 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E10 = (15.134 + c10 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E11 = (119.743 + c11 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E12 = (239.961 + c12 * d) * CesiumMath.RADIANS_PER_DEGREE;
var E13 = (25.053 + c13 * d) * CesiumMath.RADIANS_PER_DEGREE;
var sinE1 = Math.sin(E1);
var sinE2 = Math.sin(E2);
var sinE3 = Math.sin(E3);
var sinE4 = Math.sin(E4);
var sinE5 = Math.sin(E5);
var sinE6 = Math.sin(E6);
var sinE7 = Math.sin(E7);
var sinE8 = Math.sin(E8);
var sinE9 = Math.sin(E9);
var sinE10 = Math.sin(E10);
var sinE11 = Math.sin(E11);
var sinE12 = Math.sin(E12);
var sinE13 = Math.sin(E13);
var cosE1 = Math.cos(E1);
var cosE2 = Math.cos(E2);
var cosE3 = Math.cos(E3);
var cosE4 = Math.cos(E4);
var cosE5 = Math.cos(E5);
var cosE6 = Math.cos(E6);
var cosE7 = Math.cos(E7);
var cosE8 = Math.cos(E8);
var cosE9 = Math.cos(E9);
var cosE10 = Math.cos(E10);
var cosE11 = Math.cos(E11);
var cosE12 = Math.cos(E12);
var cosE13 = Math.cos(E13);
var rightAscension = (269.9949 + 0.0031 * T - 3.8787 * sinE1 - 0.1204 * sinE2 +
0.0700 * sinE3 - 0.0172 * sinE4 + 0.0072 * sinE6 -
0.0052 * sinE10 + 0.0043 * sinE13) *
CesiumMath.RADIANS_PER_DEGREE;
var declination = (66.5392 + 0.013 * T + 1.5419 * cosE1 + 0.0239 * cosE2 -
0.0278 * cosE3 + 0.0068 * cosE4 - 0.0029 * cosE6 +
0.0009 * cosE7 + 0.0008 * cosE10 - 0.0009 * cosE13) *
CesiumMath.RADIANS_PER_DEGREE;
var rotation = (38.3213 + 13.17635815 * d - 1.4e-12 * d * d + 3.5610 * sinE1 +
0.1208 * sinE2 - 0.0642 * sinE3 + 0.0158 * sinE4 +
0.0252 * sinE5 - 0.0066 * sinE6 - 0.0047 * sinE7 -
0.0046 * sinE8 + 0.0028 * sinE9 + 0.0052 * sinE10 +
0.004 * sinE11 + 0.0019 * sinE12 - 0.0044 * sinE13) *
CesiumMath.RADIANS_PER_DEGREE;
var rotationRate = ((13.17635815 - 1.4e-12 * (2.0 * d)) +
3.5610 * cosE1 * c1 +
0.1208 * cosE2*c2 - 0.0642 * cosE3*c3 + 0.0158 * cosE4*c4 +
0.0252 * cosE5*c5 - 0.0066 * cosE6*c6 - 0.0047 * cosE7*c7 -
0.0046 * cosE8*c8 + 0.0028 * cosE9*c9 + 0.0052 * cosE10*c10 +
0.004 * cosE11*c11 + 0.0019 * cosE12*c12 - 0.0044 * cosE13*c13) /
86400.0 * CesiumMath.RADIANS_PER_DEGREE;
if (!defined(result)) {
result = new IauOrientationParameters();
}
result.rightAscension = rightAscension;
result.declination = declination;
result.rotation = rotation;
result.rotationRate = rotationRate;
return result;
};
return Iau2000Orientation;
});
/*global define*/
define('Core/IauOrientationAxes',[
'./Cartesian3',
'./defined',
'./Iau2000Orientation',
'./JulianDate',
'./Math',
'./Matrix3',
'./Quaternion'
], function(
Cartesian3,
defined,
Iau2000Orientation,
JulianDate,
CesiumMath,
Matrix3,
Quaternion) {
'use strict';
/**
* The Axes representing the orientation of a Globe as represented by the data
* from the IAU/IAG Working Group reports on rotational elements.
* @alias IauOrientationAxes
* @constructor
*
* @param {IauOrientationAxes~ComputeFunction} [computeFunction] The function that computes the {@link IauOrientationParameters} given a {@link JulianDate}.
*
* @see Iau2000Orientation
*
* @private
*/
function IauOrientationAxes(computeFunction) {
if (!defined(computeFunction) || typeof computeFunction !== 'function') {
computeFunction = Iau2000Orientation.ComputeMoon;
}
this._computeFunction = computeFunction;
}
var xAxisScratch = new Cartesian3();
var yAxisScratch = new Cartesian3();
var zAxisScratch = new Cartesian3();
function computeRotationMatrix(alpha, delta, result) {
var xAxis = xAxisScratch;
xAxis.x = Math.cos(alpha + CesiumMath.PI_OVER_TWO);
xAxis.y = Math.sin(alpha + CesiumMath.PI_OVER_TWO);
xAxis.z = 0.0;
var cosDec = Math.cos(delta);
var zAxis = zAxisScratch;
zAxis.x = cosDec * Math.cos(alpha);
zAxis.y = cosDec * Math.sin(alpha);
zAxis.z = Math.sin(delta);
var yAxis = Cartesian3.cross(zAxis, xAxis, yAxisScratch);
if (!defined(result)) {
result = new Matrix3();
}
result[0] = xAxis.x;
result[1] = yAxis.x;
result[2] = zAxis.x;
result[3] = xAxis.y;
result[4] = yAxis.y;
result[5] = zAxis.y;
result[6] = xAxis.z;
result[7] = yAxis.z;
result[8] = zAxis.z;
return result;
}
var rotMtxScratch = new Matrix3();
var quatScratch = new Quaternion();
/**
* Computes a rotation from ICRF to a Globe's Fixed axes.
*
* @param {JulianDate} date The date to evaluate the matrix.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix} The modified result parameter or a new instance of the rotation from ICRF to Fixed.
*/
IauOrientationAxes.prototype.evaluate = function(date, result) {
if (!defined(date)) {
date = JulianDate.now();
}
var alphaDeltaW = this._computeFunction(date);
var precMtx = computeRotationMatrix(alphaDeltaW.rightAscension, alphaDeltaW.declination, result);
var rot = CesiumMath.zeroToTwoPi(alphaDeltaW.rotation);
var quat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, rot, quatScratch);
var rotMtx = Matrix3.fromQuaternion(Quaternion.conjugate(quat, quat), rotMtxScratch);
var cbi2cbf = Matrix3.multiply(rotMtx, precMtx, precMtx);
return cbi2cbf;
};
/**
* A function that computes the {@link IauOrientationParameters} for a {@link JulianDate}.
* @callback IauOrientationAxes~ComputeFunction
* @param {JulianDate} date The date to evaluate the parameters.
* @returns {IauOrientationParameters} The orientation parameters.
*/
return IauOrientationAxes;
});
/*global define*/
define('Core/InterpolationAlgorithm',[
'./DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* The interface for interpolation algorithms.
*
* @exports InterpolationAlgorithm
*
* @see LagrangePolynomialApproximation
* @see LinearApproximation
* @see HermitePolynomialApproximation
*/
var InterpolationAlgorithm = {};
/**
* Gets the name of this interpolation algorithm.
* @type {String}
*/
InterpolationAlgorithm.type = undefined;
/**
* Given the desired degree, returns the number of data points required for interpolation.
* @function
*
* @param {Number} degree The desired degree of interpolation.
* @returns {Number} The number of required data points needed for the desired degree of interpolation.
*/
InterpolationAlgorithm.getRequiredDataPoints = DeveloperError.throwInstantiationError;
/**
* Performs zero order interpolation.
* @function
*
* @param {Number} x The independent variable for which the dependent variables will be interpolated.
* @param {Number[]} xTable The array of independent variables to use to interpolate. The values
* in this array must be in increasing order and the same value must not occur twice in the array.
* @param {Number[]} yTable The array of dependent variables to use to interpolate. For a set of three
* dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
* @param {Number} yStride The number of dependent variable values in yTable corresponding to
* each independent variable value in xTable.
* @param {Number[]} [result] An existing array into which to store the result.
*
* @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
*/
InterpolationAlgorithm.interpolateOrderZero = DeveloperError.throwInstantiationError;
/**
* Performs higher order interpolation. Not all interpolators need to support high-order interpolation,
* if this function remains undefined on implementing objects, interpolateOrderZero will be used instead.
* @function
* @param {Number} x The independent variable for which the dependent variables will be interpolated.
* @param {Number[]} xTable The array of independent variables to use to interpolate. The values
* in this array must be in increasing order and the same value must not occur twice in the array.
* @param {Number[]} yTable The array of dependent variables to use to interpolate. For a set of three
* dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
* @param {Number} yStride The number of dependent variable values in yTable corresponding to
* each independent variable value in xTable.
* @param {Number} inputOrder The number of derivatives supplied for input.
* @param {Number} outputOrder The number of derivatives desired for output.
* @param {Number[]} [result] An existing array into which to store the result.
* @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
*/
InterpolationAlgorithm.interpolate = DeveloperError.throwInstantiationError;
return InterpolationAlgorithm;
});
/*global define*/
define('Core/TimeInterval',[
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./freezeObject',
'./JulianDate'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
freezeObject,
JulianDate) {
'use strict';
/**
* An interval defined by a start and a stop time; optionally including those times as part of the interval.
* Arbitrary data can optionally be associated with each instance for used with {@link TimeIntervalCollection}.
*
* @alias TimeInterval
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {JulianDate} [options.start=new JulianDate()] The start time of the interval.
* @param {JulianDate} [options.stop=new JulianDate()] The stop time of the interval.
* @param {Boolean} [options.isStartIncluded=true] true
if options.start
is included in the interval, false
otherwise.
* @param {Boolean} [options.isStopIncluded=true] true
if options.stop
is included in the interval, false
otherwise.
* @param {Object} [options.data] Arbitrary data associated with this interval.
*
* @example
* // Create an instance that spans August 1st, 1980 and is associated
* // with a Cartesian position.
* var timeInterval = new Cesium.TimeInterval({
* start : Cesium.JulianDate.fromIso8601('1980-08-01T00:00:00Z'),
* stop : Cesium.JulianDate.fromIso8601('1980-08-02T00:00:00Z'),
* isStartIncluded : true,
* isStopIncluded : false,
* data : Cesium.Cartesian3.fromDegrees(39.921037, -75.170082)
* });
*
* @example
* // Create two instances from ISO 8601 intervals with associated numeric data
* // then compute their intersection, summing the data they contain.
* var left = Cesium.TimeInterval.fromIso8601({
* iso8601 : '2000/2010',
* data : 2
* });
*
* var right = Cesium.TimeInterval.fromIso8601({
* iso8601 : '1995/2005',
* data : 3
* });
*
* //The result of the below intersection will be an interval equivalent to
* //var intersection = Cesium.TimeInterval.fromIso8601({
* // iso8601 : '2000/2005',
* // data : 5
* //});
* var intersection = new Cesium.TimeInterval();
* Cesium.TimeInterval.intersect(left, right, intersection, function(leftData, rightData) {
* return leftData + rightData;
* });
*
* @example
* // Check if an interval contains a specific time.
* var dateToCheck = Cesium.JulianDate.fromIso8601('1982-09-08T11:30:00Z');
* var containsDate = Cesium.TimeInterval.contains(timeInterval, dateToCheck);
*/
function TimeInterval(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* Gets or sets the start time of this interval.
* @type {JulianDate}
*/
this.start = defined(options.start) ? JulianDate.clone(options.start) : new JulianDate();
/**
* Gets or sets the stop time of this interval.
* @type {JulianDate}
*/
this.stop = defined(options.stop) ? JulianDate.clone(options.stop) : new JulianDate();
/**
* Gets or sets the data associated with this interval.
* @type {Object}
*/
this.data = options.data;
/**
* Gets or sets whether or not the start time is included in this interval.
* @type {Boolean}
* @default true
*/
this.isStartIncluded = defaultValue(options.isStartIncluded, true);
/**
* Gets or sets whether or not the stop time is included in this interval.
* @type {Boolean}
* @default true
*/
this.isStopIncluded = defaultValue(options.isStopIncluded, true);
}
defineProperties(TimeInterval.prototype, {
/**
* Gets whether or not this interval is empty.
* @memberof TimeInterval.prototype
* @type {Boolean}
* @readonly
*/
isEmpty : {
get : function() {
var stopComparedToStart = JulianDate.compare(this.stop, this.start);
return stopComparedToStart < 0 || (stopComparedToStart === 0 && (!this.isStartIncluded || !this.isStopIncluded));
}
}
});
var scratchInterval = {
start : undefined,
stop : undefined,
isStartIncluded : undefined,
isStopIncluded : undefined,
data : undefined
};
/**
* Creates a new instance from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} interval.
*
* @param {Object} options Object with the following properties:
* @param {String} options.iso8601 An ISO 8601 interval.
* @param {Boolean} [options.isStartIncluded=true] true
if options.start
is included in the interval, false
otherwise.
* @param {Boolean} [options.isStopIncluded=true] true
if options.stop
is included in the interval, false
otherwise.
* @param {Object} [options.data] Arbitrary data associated with this interval.
* @param {TimeInterval} [result] An existing instance to use for the result.
* @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
*/
TimeInterval.fromIso8601 = function(options, result) {
if (!defined(options)) {
throw new DeveloperError('options is required.');
}
if (!defined(options.iso8601)) {
throw new DeveloperError('options.iso8601 is required.');
}
var dates = options.iso8601.split('/');
var start = JulianDate.fromIso8601(dates[0]);
var stop = JulianDate.fromIso8601(dates[1]);
var isStartIncluded = defaultValue(options.isStartIncluded, true);
var isStopIncluded = defaultValue(options.isStopIncluded, true);
var data = options.data;
if (!defined(result)) {
scratchInterval.start = start;
scratchInterval.stop = stop;
scratchInterval.isStartIncluded = isStartIncluded;
scratchInterval.isStopIncluded = isStopIncluded;
scratchInterval.data = data;
return new TimeInterval(scratchInterval);
}
result.start = start;
result.stop = stop;
result.isStartIncluded = isStartIncluded;
result.isStopIncluded = isStopIncluded;
result.data = data;
return result;
};
/**
* Creates an ISO8601 representation of the provided interval.
*
* @param {TimeInterval} timeInterval The interval to be converted.
* @param {Number} [precision] The number of fractional digits used to represent the seconds component. By default, the most precise representation is used.
* @returns {String} The ISO8601 representation of the provided interval.
*/
TimeInterval.toIso8601 = function(timeInterval, precision) {
if (!defined(timeInterval)) {
throw new DeveloperError('timeInterval is required.');
}
return JulianDate.toIso8601(timeInterval.start, precision) + '/' + JulianDate.toIso8601(timeInterval.stop, precision);
};
/**
* Duplicates the provided instance.
*
* @param {TimeInterval} [timeInterval] The instance to clone.
* @param {TimeInterval} [result] An existing instance to use for the result.
* @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
*/
TimeInterval.clone = function(timeInterval, result) {
if (!defined(timeInterval)) {
return undefined;
}
if (!defined(result)) {
return new TimeInterval(timeInterval);
}
result.start = timeInterval.start;
result.stop = timeInterval.stop;
result.isStartIncluded = timeInterval.isStartIncluded;
result.isStopIncluded = timeInterval.isStopIncluded;
result.data = timeInterval.data;
return result;
};
/**
* Compares two instances and returns true
if they are equal, false
otherwise.
*
* @param {TimeInterval} [left] The first instance.
* @param {TimeInterval} [right] The second instance.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
* @returns {Boolean} true
if the dates are equal; otherwise, false
.
*/
TimeInterval.equals = function(left, right, dataComparer) {
return left === right ||
defined(left) && defined(right) &&
(left.isEmpty && right.isEmpty ||
left.isStartIncluded === right.isStartIncluded &&
left.isStopIncluded === right.isStopIncluded &&
JulianDate.equals(left.start, right.start) &&
JulianDate.equals(left.stop, right.stop) &&
(left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data))));
};
/**
* Compares two instances and returns true
if they are within epsilon
seconds of
* each other. That is, in order for the dates to be considered equal (and for
* this function to return true
), the absolute value of the difference between them, in
* seconds, must be less than epsilon
.
*
* @param {TimeInterval} [left] The first instance.
* @param {TimeInterval} [right] The second instance.
* @param {Number} epsilon The maximum number of seconds that should separate the two instances.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
* @returns {Boolean} true
if the two dates are within epsilon
seconds of each other; otherwise false
.
*/
TimeInterval.equalsEpsilon = function(left, right, epsilon, dataComparer) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon is required and must be a number.');
}
return left === right ||
defined(left) && defined(right) &&
(left.isEmpty && right.isEmpty ||
left.isStartIncluded === right.isStartIncluded &&
left.isStopIncluded === right.isStopIncluded &&
JulianDate.equalsEpsilon(left.start, right.start, epsilon) &&
JulianDate.equalsEpsilon(left.stop, right.stop, epsilon) &&
(left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data))));
};
/**
* Computes the intersection of two intervals, optionally merging their data.
*
* @param {TimeInterval} left The first interval.
* @param {TimeInterval} [right] The second interval.
* @param {TimeInterval} result An existing instance to use for the result.
* @param {TimeInterval~MergeCallback} [mergeCallback] A function which merges the data of the two intervals. If omitted, the data from the left interval will be used.
* @returns {TimeInterval} The modified result parameter.
*/
TimeInterval.intersect = function(left, right, result, mergeCallback) {
if (!defined(left)) {
throw new DeveloperError('left is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
if (!defined(right)) {
return TimeInterval.clone(TimeInterval.EMPTY, result);
}
var leftStart = left.start;
var leftStop = left.stop;
var rightStart = right.start;
var rightStop = right.stop;
var intersectsStartRight = JulianDate.greaterThanOrEquals(rightStart, leftStart) && JulianDate.greaterThanOrEquals(leftStop, rightStart);
var intersectsStartLeft = !intersectsStartRight && JulianDate.lessThanOrEquals(rightStart, leftStart) && JulianDate.lessThanOrEquals(leftStart, rightStop);
if (!intersectsStartRight && !intersectsStartLeft) {
return TimeInterval.clone(TimeInterval.EMPTY, result);
}
var leftIsStartIncluded = left.isStartIncluded;
var leftIsStopIncluded = left.isStopIncluded;
var rightIsStartIncluded = right.isStartIncluded;
var rightIsStopIncluded = right.isStopIncluded;
var leftLessThanRight = JulianDate.lessThan(leftStop, rightStop);
result.start = intersectsStartRight ? rightStart : leftStart;
result.isStartIncluded = (leftIsStartIncluded && rightIsStartIncluded) || (!JulianDate.equals(rightStart, leftStart) && ((intersectsStartRight && rightIsStartIncluded) || (intersectsStartLeft && leftIsStartIncluded)));
result.stop = leftLessThanRight ? leftStop : rightStop;
result.isStopIncluded = leftLessThanRight ? leftIsStopIncluded : (leftIsStopIncluded && rightIsStopIncluded) || (!JulianDate.equals(rightStop, leftStop) && rightIsStopIncluded);
result.data = defined(mergeCallback) ? mergeCallback(left.data, right.data) : left.data;
return result;
};
/**
* Checks if the specified date is inside the provided interval.
*
* @param {TimeInterval} timeInterval The interval.
* @param {JulianDate} julianDate The date to check.
* @returns {Boolean} true
if the interval contains the specified date, false
otherwise.
*/
TimeInterval.contains = function(timeInterval, julianDate) {
if (!defined(timeInterval)) {
throw new DeveloperError('timeInterval is required.');
}
if (!defined(julianDate)) {
throw new DeveloperError('julianDate is required.');
}
if (timeInterval.isEmpty) {
return false;
}
var startComparedToDate = JulianDate.compare(timeInterval.start, julianDate);
if (startComparedToDate === 0) {
return timeInterval.isStartIncluded;
}
var dateComparedToStop = JulianDate.compare(julianDate, timeInterval.stop);
if (dateComparedToStop === 0) {
return timeInterval.isStopIncluded;
}
return startComparedToDate < 0 && dateComparedToStop < 0;
};
/**
* Duplicates this instance.
*
* @param {TimeInterval} [result] An existing instance to use for the result.
* @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
*/
TimeInterval.prototype.clone = function(result) {
return TimeInterval.clone(this, result);
};
/**
* Compares this instance against the provided instance componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {TimeInterval} [right] The right hand side interval.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
TimeInterval.prototype.equals = function(right, dataComparer) {
return TimeInterval.equals(this, right, dataComparer);
};
/**
* Compares this instance against the provided instance componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {TimeInterval} [right] The right hand side interval.
* @param {Number} epsilon The epsilon to use for equality testing.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
TimeInterval.prototype.equalsEpsilon = function(right, epsilon, dataComparer) {
return TimeInterval.equalsEpsilon(this, right, epsilon, dataComparer);
};
/**
* Creates a string representing this TimeInterval in ISO8601 format.
*
* @returns {String} A string representing this TimeInterval in ISO8601 format.
*/
TimeInterval.prototype.toString = function() {
return TimeInterval.toIso8601(this);
};
/**
* An immutable empty interval.
*
* @type {TimeInterval}
* @constant
*/
TimeInterval.EMPTY = freezeObject(new TimeInterval({
start : new JulianDate(),
stop : new JulianDate(),
isStartIncluded : false,
isStopIncluded : false
}));
/**
* Function interface for merging interval data.
* @callback TimeInterval~MergeCallback
*
* @param {Object} leftData The first data instance.
* @param {Object} rightData The second data instance.
* @returns {Object} The result of merging the two data instances.
*/
/**
* Function interface for comparing interval data.
* @callback TimeInterval~DataComparer
* @param {Object} leftData The first data instance.
* @param {Object} rightData The second data instance.
* @returns {Boolean} true
if the provided instances are equal, false
otherwise.
*/
return TimeInterval;
});
/*global define*/
define('Core/Iso8601',[
'./freezeObject',
'./JulianDate',
'./TimeInterval'
], function(
freezeObject,
JulianDate,
TimeInterval) {
'use strict';
var MINIMUM_VALUE = freezeObject(JulianDate.fromIso8601('0000-01-01T00:00:00Z'));
var MAXIMUM_VALUE = freezeObject(JulianDate.fromIso8601('9999-12-31T24:00:00Z'));
var MAXIMUM_INTERVAL = freezeObject(new TimeInterval({
start : MINIMUM_VALUE,
stop : MAXIMUM_VALUE
}));
/**
* Constants related to ISO8601 support.
*
* @exports Iso8601
*
* @see {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601 on Wikipedia}
* @see JulianDate
* @see TimeInterval
*/
var Iso8601 = {
/**
* A {@link JulianDate} representing the earliest time representable by an ISO8601 date.
* This is equivalent to the date string '0000-01-01T00:00:00Z'
*/
MINIMUM_VALUE : MINIMUM_VALUE,
/**
* A {@link JulianDate} representing the latest time representable by an ISO8601 date.
* This is equivalent to the date string '9999-12-31T24:00:00Z'
*/
MAXIMUM_VALUE : MAXIMUM_VALUE,
/**
* A {@link TimeInterval} representing the largest interval representable by an ISO8601 interval.
* This is equivalent to the interval string '0000-01-01T00:00:00Z/9999-12-31T24:00:00Z'
*/
MAXIMUM_INTERVAL : MAXIMUM_INTERVAL
};
return Iso8601;
});
/*global define*/
define('Core/KeyboardEventModifier',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* This enumerated type is for representing keyboard modifiers. These are keys
* that are held down in addition to other event types.
*
* @exports KeyboardEventModifier
*/
var KeyboardEventModifier = {
/**
* Represents the shift key being held down.
*
* @type {Number}
* @constant
*/
SHIFT : 0,
/**
* Represents the control key being held down.
*
* @type {Number}
* @constant
*/
CTRL : 1,
/**
* Represents the alt key being held down.
*
* @type {Number}
* @constant
*/
ALT : 2
};
return freezeObject(KeyboardEventModifier);
});
/*global define*/
define('Core/LagrangePolynomialApproximation',[
'./defined'
], function(
defined) {
'use strict';
/**
* An {@link InterpolationAlgorithm} for performing Lagrange interpolation.
*
* @exports LagrangePolynomialApproximation
*/
var LagrangePolynomialApproximation = {
type : 'Lagrange'
};
/**
* Given the desired degree, returns the number of data points required for interpolation.
*
* @param {Number} degree The desired degree of interpolation.
* @returns {Number} The number of required data points needed for the desired degree of interpolation.
*/
LagrangePolynomialApproximation.getRequiredDataPoints = function(degree) {
return Math.max(degree + 1.0, 2);
};
/**
* Interpolates values using Lagrange Polynomial Approximation.
*
* @param {Number} x The independent variable for which the dependent variables will be interpolated.
* @param {Number[]} xTable The array of independent variables to use to interpolate. The values
* in this array must be in increasing order and the same value must not occur twice in the array.
* @param {Number[]} yTable The array of dependent variables to use to interpolate. For a set of three
* dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
* @param {Number} yStride The number of dependent variable values in yTable corresponding to
* each independent variable value in xTable.
* @param {Number[]} [result] An existing array into which to store the result.
* @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
*/
LagrangePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
if (!defined(result)) {
result = new Array(yStride);
}
var i;
var j;
var length = xTable.length;
for (i = 0; i < yStride; i++) {
result[i] = 0;
}
for (i = 0; i < length; i++) {
var coefficient = 1;
for (j = 0; j < length; j++) {
if (j !== i) {
var diffX = xTable[i] - xTable[j];
coefficient *= (x - xTable[j]) / diffX;
}
}
for (j = 0; j < yStride; j++) {
result[j] += coefficient * yTable[i * yStride + j];
}
}
return result;
};
return LagrangePolynomialApproximation;
});
/*global define*/
define('Core/LinearApproximation',[
'./defined',
'./DeveloperError'
], function(
defined,
DeveloperError) {
'use strict';
/**
* An {@link InterpolationAlgorithm} for performing linear interpolation.
*
* @exports LinearApproximation
*/
var LinearApproximation = {
type : 'Linear'
};
/**
* Given the desired degree, returns the number of data points required for interpolation.
* Since linear interpolation can only generate a first degree polynomial, this function
* always returns 2.
* @param {Number} degree The desired degree of interpolation.
* @returns {Number} This function always returns 2.
*
*/
LinearApproximation.getRequiredDataPoints = function(degree) {
return 2;
};
/**
* Interpolates values using linear approximation.
*
* @param {Number} x The independent variable for which the dependent variables will be interpolated.
* @param {Number[]} xTable The array of independent variables to use to interpolate. The values
* in this array must be in increasing order and the same value must not occur twice in the array.
* @param {Number[]} yTable The array of dependent variables to use to interpolate. For a set of three
* dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
* @param {Number} yStride The number of dependent variable values in yTable corresponding to
* each independent variable value in xTable.
* @param {Number[]} [result] An existing array into which to store the result.
* @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
*/
LinearApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
if (xTable.length !== 2) {
throw new DeveloperError('The xTable provided to the linear interpolator must have exactly two elements.');
} else if (yStride <= 0) {
throw new DeveloperError('There must be at least 1 dependent variable for each independent variable.');
}
if (!defined(result)) {
result = new Array(yStride);
}
var i;
var y0;
var y1;
var x0 = xTable[0];
var x1 = xTable[1];
if (x0 === x1) {
throw new DeveloperError('Divide by zero error: xTable[0] and xTable[1] are equal');
}
for (i = 0; i < yStride; i++) {
y0 = yTable[i];
y1 = yTable[i + yStride];
result[i] = (((y1 - y0) * x) + (x1 * y0) - (x0 * y1)) / (x1 - x0);
}
return result;
};
return LinearApproximation;
});
/*global define*/
define('Core/loadBlob',[
'./loadWithXhr'
], function(
loadWithXhr) {
'use strict';
/**
* Asynchronously loads the given URL as a blob. Returns a promise that will resolve to
* a Blob once loaded, or reject if the URL failed to load. The data is loaded
* using XMLHttpRequest, which means that in order to make requests to another origin,
* the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
*
* @exports loadBlob
*
* @param {String|Promise.} url The URL of the data, or a promise for the URL.
* @param {Object} [headers] HTTP headers to send with the requests.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load a single URL asynchronously
* Cesium.loadBlob('some/url').then(function(blob) {
* // use the data
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadBlob(url, headers) {
return loadWithXhr({
url : url,
responseType : 'blob',
headers : headers
});
}
return loadBlob;
});
/*global define*/
define('Core/loadImageFromTypedArray',[
'../ThirdParty/when',
'./defined',
'./DeveloperError',
'./loadImage'
], function(
when,
defined,
DeveloperError,
loadImage) {
'use strict';
/**
* @private
*/
function loadImageFromTypedArray(uint8Array, format) {
if (!defined(uint8Array)) {
throw new DeveloperError('uint8Array is required.');
}
if (!defined(format)) {
throw new DeveloperError('format is required.');
}
var blob = new Blob([uint8Array], {
type : format
});
var blobUrl = window.URL.createObjectURL(blob);
return loadImage(blobUrl, false).then(function(image) {
window.URL.revokeObjectURL(blobUrl);
return image;
}, function(error) {
window.URL.revokeObjectURL(blobUrl);
return when.reject(error);
});
}
return loadImageFromTypedArray;
});
/*global define*/
define('Core/loadImageViaBlob',[
'../ThirdParty/when',
'./loadBlob',
'./loadImage'
], function(
when,
loadBlob,
loadImage) {
'use strict';
var dataUriRegex = /^data:/;
/**
* Asynchronously loads the given image URL by first downloading it as a blob using
* XMLHttpRequest and then loading the image from the buffer via a blob URL.
* This allows access to more information that is not accessible via normal
* Image-based downloading, such as the size of the response. This function
* returns a promise that will resolve to
* an {@link Image} once loaded, or reject if the image failed to load. The
* returned image will have a "blob" property with the Blob itself. If the browser
* does not support an XMLHttpRequests with a responseType of 'blob', or if the
* provided URI is a data URI, this function is equivalent to calling {@link loadImage},
* and the extra blob property will not be present.
*
* @exports loadImageViaBlob
*
* @param {String|Promise.} url The source of the image, or a promise for the URL.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load a single image asynchronously
* Cesium.loadImageViaBlob('some/image/url.png').then(function(image) {
* var blob = image.blob;
* // use the loaded image or XHR
* }).otherwise(function(error) {
* // an error occurred
* });
*
* // load several images in parallel
* when.all([loadImageViaBlob('image1.png'), loadImageViaBlob('image2.png')]).then(function(images) {
* // images is an array containing all the loaded images
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadImageViaBlob(url) {
if (dataUriRegex.test(url)) {
return loadImage(url);
}
return loadBlob(url).then(function(blob) {
var blobUrl = window.URL.createObjectURL(blob);
return loadImage(blobUrl, false).then(function(image) {
image.blob = blob;
window.URL.revokeObjectURL(blobUrl);
return image;
}, function(error) {
window.URL.revokeObjectURL(blobUrl);
return when.reject(error);
});
});
}
var xhrBlobSupported = (function() {
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', '#', true);
xhr.responseType = 'blob';
return xhr.responseType === 'blob';
} catch (e) {
return false;
}
})();
return xhrBlobSupported ? loadImageViaBlob : loadImage;
});
/*global define*/
define('Core/objectToQuery',[
'./defined',
'./DeveloperError',
'./isArray'
], function(
defined,
DeveloperError,
isArray) {
'use strict';
/**
* Converts an object representing a set of name/value pairs into a query string,
* with names and values encoded properly for use in a URL. Values that are arrays
* will produce multiple values with the same name.
* @exports objectToQuery
*
* @param {Object} obj The object containing data to encode.
* @returns {String} An encoded query string.
*
*
* @example
* var str = Cesium.objectToQuery({
* key1 : 'some value',
* key2 : 'a/b',
* key3 : ['x', 'y']
* });
*
* @see queryToObject
* // str will be:
* // 'key1=some%20value&key2=a%2Fb&key3=x&key3=y'
*/
function objectToQuery(obj) {
if (!defined(obj)) {
throw new DeveloperError('obj is required.');
}
var result = '';
for ( var propName in obj) {
if (obj.hasOwnProperty(propName)) {
var value = obj[propName];
var part = encodeURIComponent(propName) + '=';
if (isArray(value)) {
for (var i = 0, len = value.length; i < len; ++i) {
result += part + encodeURIComponent(value[i]) + '&';
}
} else {
result += part + encodeURIComponent(value) + '&';
}
}
}
// trim last &
result = result.slice(0, -1);
// This function used to replace %20 with + which is more compact and readable.
// However, some servers didn't properly handle + as a space.
// https://github.com/AnalyticalGraphicsInc/cesium/issues/2192
return result;
}
return objectToQuery;
});
/*global define*/
define('Core/queryToObject',[
'./defined',
'./DeveloperError',
'./isArray'
], function(
defined,
DeveloperError,
isArray) {
'use strict';
/**
* Parses a query string into an object, where the keys and values of the object are the
* name/value pairs from the query string, decoded. If a name appears multiple times,
* the value in the object will be an array of values.
* @exports queryToObject
*
* @param {String} queryString The query string.
* @returns {Object} An object containing the parameters parsed from the query string.
*
*
* @example
* var obj = Cesium.queryToObject('key1=some%20value&key2=a%2Fb&key3=x&key3=y');
* // obj will be:
* // {
* // key1 : 'some value',
* // key2 : 'a/b',
* // key3 : ['x', 'y']
* // }
*
* @see objectToQuery
*/
function queryToObject(queryString) {
if (!defined(queryString)) {
throw new DeveloperError('queryString is required.');
}
var result = {};
if (queryString === '') {
return result;
}
var parts = queryString.replace(/\+/g, '%20').split('&');
for (var i = 0, len = parts.length; i < len; ++i) {
var subparts = parts[i].split('=');
var name = decodeURIComponent(subparts[0]);
var value = subparts[1];
if (defined(value)) {
value = decodeURIComponent(value);
} else {
value = '';
}
var resultValue = result[name];
if (typeof resultValue === 'string') {
// expand the single value to an array
result[name] = [resultValue, value];
} else if (isArray(resultValue)) {
resultValue.push(value);
} else {
result[name] = value;
}
}
return result;
}
return queryToObject;
});
/*global define*/
define('Core/loadJsonp',[
'../ThirdParty/Uri',
'../ThirdParty/when',
'./combine',
'./defaultValue',
'./defined',
'./DeveloperError',
'./objectToQuery',
'./queryToObject'
], function(
Uri,
when,
combine,
defaultValue,
defined,
DeveloperError,
objectToQuery,
queryToObject) {
'use strict';
/**
* Requests a resource using JSONP.
*
* @exports loadJsonp
*
* @param {String} url The URL to request.
* @param {Object} [options] Object with the following properties:
* @param {Object} [options.parameters] Any extra query parameters to append to the URL.
* @param {String} [options.callbackParameterName='callback'] The callback parameter name that the server expects.
* @param {Proxy} [options.proxy] A proxy to use for the request. This object is expected to have a getURL function which returns the proxied URL, if needed.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load a data asynchronously
* Cesium.loadJsonp('some/webservice').then(function(data) {
* // use the loaded data
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadJsonp(url, options) {
if (!defined(url)) {
throw new DeveloperError('url is required.');
}
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//generate a unique function name
var functionName;
do {
functionName = 'loadJsonp' + Math.random().toString().substring(2, 8);
} while (defined(window[functionName]));
var deferred = when.defer();
//assign a function with that name in the global scope
window[functionName] = function(data) {
deferred.resolve(data);
try {
delete window[functionName];
} catch (e) {
window[functionName] = undefined;
}
};
var uri = new Uri(url);
var queryOptions = queryToObject(defaultValue(uri.query, ''));
if (defined(options.parameters)) {
queryOptions = combine(options.parameters, queryOptions);
}
var callbackParameterName = defaultValue(options.callbackParameterName, 'callback');
queryOptions[callbackParameterName] = functionName;
uri.query = objectToQuery(queryOptions);
url = uri.toString();
var proxy = options.proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
loadJsonp.loadAndExecuteScript(url, functionName, deferred);
return deferred.promise;
}
// This is broken out into a separate function so that it can be mocked for testing purposes.
loadJsonp.loadAndExecuteScript = function(url, functionName, deferred) {
var script = document.createElement('script');
script.async = true;
script.src = url;
var head = document.getElementsByTagName('head')[0];
script.onload = function() {
script.onload = undefined;
head.removeChild(script);
};
script.onerror = function(e) {
deferred.reject(e);
};
head.appendChild(script);
};
loadJsonp.defaultLoadAndExecuteScript = loadJsonp.loadAndExecuteScript;
return loadJsonp;
});
/*global define*/
define('Core/loadXML',[
'./loadWithXhr'
], function(
loadWithXhr) {
'use strict';
/**
* Asynchronously loads the given URL as XML. Returns a promise that will resolve to
* an XML Document once loaded, or reject if the URL failed to load. The data is loaded
* using XMLHttpRequest, which means that in order to make requests to another origin,
* the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
*
* @exports loadXML
*
* @param {String|Promise.} url The URL to request, or a promise for the URL.
* @param {Object} [headers] HTTP headers to send with the request.
* @returns {Promise.} a promise that will resolve to the requested data when loaded.
*
*
* @example
* // load XML from a URL, setting a custom header
* Cesium.loadXML('http://someUrl.com/someXML.xml', {
* 'X-Custom-Header' : 'some value'
* }).then(function(document) {
* // Do something with the document
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
function loadXML(url, headers) {
return loadWithXhr({
url : url,
responseType : 'document',
headers : headers,
overrideMimeType : 'text/xml'
});
}
return loadXML;
});
/*global define*/
define('Core/MapboxApi',[
'./defined',
'./Credit'
], function(
defined,
Credit) {
'use strict';
var MapboxApi = {
};
/**
* The default Mapbox API access token to use if one is not provided to the
* constructor of an object that uses the Mapbox API. If this property is undefined,
* Cesium's default access token is used, which is only suitable for use early in development.
* Please supply your own access token as soon as possible and prior to deployment.
* Visit {@link https://www.mapbox.com/help/create-api-access-token/} for details.
* When Cesium's default access token is used, a message is printed to the console the first
* time the Mapbox API is used.
*
* @type {String}
*/
MapboxApi.defaultAccessToken = undefined;
var printedMapboxWarning = false;
var errorCredit;
var errorString = 'This application is using Cesium\'s default Mapbox access token. Please create a new access token for the application as soon as possible and prior to deployment by visiting https://www.mapbox.com/account/apps/, and provide your token to Cesium by setting the Cesium.MapboxApi.defaultAccessToken property before constructing the CesiumWidget or any other object that uses the Mapbox API.';
MapboxApi.getAccessToken = function(providedToken) {
if (defined(providedToken)) {
return providedToken;
}
if (!defined(MapboxApi.defaultAccessToken)) {
if (!printedMapboxWarning) {
console.log(errorString);
printedMapboxWarning = true;
}
return 'pk.eyJ1IjoiYW5hbHl0aWNhbGdyYXBoaWNzIiwiYSI6ImNpd204Zm4wejAwNzYyeW5uNjYyZmFwdWEifQ.7i-VIZZWX8pd1bTfxIVj9g';
}
return MapboxApi.defaultAccessToken;
};
MapboxApi.getErrorCredit = function(providedToken) {
if (defined(providedToken) || defined(MapboxApi.defaultAccessToken)) {
return undefined;
}
if (!defined(errorCredit)) {
errorCredit = new Credit(errorString);
}
return errorCredit;
};
return MapboxApi;
});
/*global define*/
define('Core/MapProjection',[
'./defineProperties',
'./DeveloperError'
], function(
defineProperties,
DeveloperError) {
'use strict';
/**
* Defines how geodetic ellipsoid coordinates ({@link Cartographic}) project to a
* flat map like Cesium's 2D and Columbus View modes.
*
* @alias MapProjection
* @constructor
*
* @see GeographicProjection
* @see WebMercatorProjection
*/
function MapProjection() {
DeveloperError.throwInstantiationError();
}
defineProperties(MapProjection.prototype, {
/**
* Gets the {@link Ellipsoid}.
*
* @memberof MapProjection.prototype
*
* @type {Ellipsoid}
* @readonly
*/
ellipsoid : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Projects {@link Cartographic} coordinates, in radians, to projection-specific map coordinates, in meters.
*
* @memberof MapProjection
* @function
*
* @param {Cartographic} cartographic The coordinates to project.
* @param {Cartesian3} [result] An instance into which to copy the result. If this parameter is
* undefined, a new instance is created and returned.
* @returns {Cartesian3} The projected coordinates. If the result parameter is not undefined, the
* coordinates are copied there and that instance is returned. Otherwise, a new instance is
* created and returned.
*/
MapProjection.prototype.project = DeveloperError.throwInstantiationError;
/**
* Unprojects projection-specific map {@link Cartesian3} coordinates, in meters, to {@link Cartographic}
* coordinates, in radians.
*
* @memberof MapProjection
* @function
*
* @param {Cartesian3} cartesian The Cartesian position to unproject with height (z) in meters.
* @param {Cartographic} [result] An instance into which to copy the result. If this parameter is
* undefined, a new instance is created and returned.
* @returns {Cartographic} The unprojected coordinates. If the result parameter is not undefined, the
* coordinates are copied there and that instance is returned. Otherwise, a new instance is
* created and returned.
*/
MapProjection.prototype.unproject = DeveloperError.throwInstantiationError;
return MapProjection;
});
/*global define*/
define('Core/Matrix2',[
'./Cartesian2',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./freezeObject'
], function(
Cartesian2,
defaultValue,
defined,
defineProperties,
DeveloperError,
freezeObject) {
'use strict';
/**
* A 2x2 matrix, indexable as a column-major order array.
* Constructor parameters are in row-major order for code readability.
* @alias Matrix2
* @constructor
*
* @param {Number} [column0Row0=0.0] The value for column 0, row 0.
* @param {Number} [column1Row0=0.0] The value for column 1, row 0.
* @param {Number} [column0Row1=0.0] The value for column 0, row 1.
* @param {Number} [column1Row1=0.0] The value for column 1, row 1.
*
* @see Matrix2.fromColumnMajorArray
* @see Matrix2.fromRowMajorArray
* @see Matrix2.fromScale
* @see Matrix2.fromUniformScale
* @see Matrix3
* @see Matrix4
*/
function Matrix2(column0Row0, column1Row0, column0Row1, column1Row1) {
this[0] = defaultValue(column0Row0, 0.0);
this[1] = defaultValue(column0Row1, 0.0);
this[2] = defaultValue(column1Row0, 0.0);
this[3] = defaultValue(column1Row1, 0.0);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
Matrix2.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {Matrix2} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
Matrix2.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value[0];
array[startingIndex++] = value[1];
array[startingIndex++] = value[2];
array[startingIndex++] = value[3];
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Matrix2} [result] The object into which to store the result.
* @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided.
*/
Matrix2.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Matrix2();
}
result[0] = array[startingIndex++];
result[1] = array[startingIndex++];
result[2] = array[startingIndex++];
result[3] = array[startingIndex++];
return result;
};
/**
* Duplicates a Matrix2 instance.
*
* @param {Matrix2} matrix The matrix to duplicate.
* @param {Matrix2} [result] The object onto which to store the result.
* @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided. (Returns undefined if matrix is undefined)
*/
Matrix2.clone = function(values, result) {
if (!defined(values)) {
return undefined;
}
if (!defined(result)) {
return new Matrix2(values[0], values[2],
values[1], values[3]);
}
result[0] = values[0];
result[1] = values[1];
result[2] = values[2];
result[3] = values[3];
return result;
};
/**
* Creates a Matrix2 from 4 consecutive elements in an array.
*
* @param {Number[]} array The array whose 4 consecutive elements correspond to the positions of the matrix. Assumes column-major order.
* @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to first column first row position in the matrix.
* @param {Matrix2} [result] The object onto which to store the result.
* @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided.
*
* @example
* // Create the Matrix2:
* // [1.0, 2.0]
* // [1.0, 2.0]
*
* var v = [1.0, 1.0, 2.0, 2.0];
* var m = Cesium.Matrix2.fromArray(v);
*
* // Create same Matrix2 with using an offset into an array
* var v2 = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];
* var m2 = Cesium.Matrix2.fromArray(v2, 2);
*/
Matrix2.fromArray = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new Matrix2();
}
result[0] = array[startingIndex];
result[1] = array[startingIndex + 1];
result[2] = array[startingIndex + 2];
result[3] = array[startingIndex + 3];
return result;
};
/**
* Creates a Matrix2 instance from a column-major order array.
*
* @param {Number[]} values The column-major order array.
* @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
*/
Matrix2.fromColumnMajorArray = function(values, result) {
if (!defined(values)) {
throw new DeveloperError('values parameter is required');
}
return Matrix2.clone(values, result);
};
/**
* Creates a Matrix2 instance from a row-major order array.
* The resulting matrix will be in column-major order.
*
* @param {Number[]} values The row-major order array.
* @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
*/
Matrix2.fromRowMajorArray = function(values, result) {
if (!defined(values)) {
throw new DeveloperError('values is required.');
}
if (!defined(result)) {
return new Matrix2(values[0], values[1],
values[2], values[3]);
}
result[0] = values[0];
result[1] = values[2];
result[2] = values[1];
result[3] = values[3];
return result;
};
/**
* Computes a Matrix2 instance representing a non-uniform scale.
*
* @param {Cartesian2} scale The x and y scale factors.
* @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
*
* @example
* // Creates
* // [7.0, 0.0]
* // [0.0, 8.0]
* var m = Cesium.Matrix2.fromScale(new Cesium.Cartesian2(7.0, 8.0));
*/
Matrix2.fromScale = function(scale, result) {
if (!defined(scale)) {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
return new Matrix2(
scale.x, 0.0,
0.0, scale.y);
}
result[0] = scale.x;
result[1] = 0.0;
result[2] = 0.0;
result[3] = scale.y;
return result;
};
/**
* Computes a Matrix2 instance representing a uniform scale.
*
* @param {Number} scale The uniform scale factor.
* @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
*
* @example
* // Creates
* // [2.0, 0.0]
* // [0.0, 2.0]
* var m = Cesium.Matrix2.fromUniformScale(2.0);
*/
Matrix2.fromUniformScale = function(scale, result) {
if (typeof scale !== 'number') {
throw new DeveloperError('scale is required.');
}
if (!defined(result)) {
return new Matrix2(
scale, 0.0,
0.0, scale);
}
result[0] = scale;
result[1] = 0.0;
result[2] = 0.0;
result[3] = scale;
return result;
};
/**
* Creates a rotation matrix.
*
* @param {Number} angle The angle, in radians, of the rotation. Positive angles are counterclockwise.
* @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
*
* @example
* // Rotate a point 45 degrees counterclockwise.
* var p = new Cesium.Cartesian2(5, 6);
* var m = Cesium.Matrix2.fromRotation(Cesium.Math.toRadians(45.0));
* var rotated = Cesium.Matrix2.multiplyByVector(m, p, new Cesium.Cartesian2());
*/
Matrix2.fromRotation = function(angle, result) {
if (!defined(angle)) {
throw new DeveloperError('angle is required.');
}
var cosAngle = Math.cos(angle);
var sinAngle = Math.sin(angle);
if (!defined(result)) {
return new Matrix2(
cosAngle, -sinAngle,
sinAngle, cosAngle);
}
result[0] = cosAngle;
result[1] = sinAngle;
result[2] = -sinAngle;
result[3] = cosAngle;
return result;
};
/**
* Creates an Array from the provided Matrix2 instance.
* The array will be in column-major order.
*
* @param {Matrix2} matrix The matrix to use..
* @param {Number[]} [result] The Array onto which to store the result.
* @returns {Number[]} The modified Array parameter or a new Array instance if one was not provided.
*/
Matrix2.toArray = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
return [matrix[0], matrix[1], matrix[2], matrix[3]];
}
result[0] = matrix[0];
result[1] = matrix[1];
result[2] = matrix[2];
result[3] = matrix[3];
return result;
};
/**
* Computes the array index of the element at the provided row and column.
*
* @param {Number} row The zero-based index of the row.
* @param {Number} column The zero-based index of the column.
* @returns {Number} The index of the element at the provided row and column.
*
* @exception {DeveloperError} row must be 0 or 1.
* @exception {DeveloperError} column must be 0 or 1.
*
* @example
* var myMatrix = new Cesium.Matrix2();
* var column1Row0Index = Cesium.Matrix2.getElementIndex(1, 0);
* var column1Row0 = myMatrix[column1Row0Index]
* myMatrix[column1Row0Index] = 10.0;
*/
Matrix2.getElementIndex = function(column, row) {
if (typeof row !== 'number' || row < 0 || row > 1) {
throw new DeveloperError('row must be 0 or 1.');
}
if (typeof column !== 'number' || column < 0 || column > 1) {
throw new DeveloperError('column must be 0 or 1.');
}
return column * 2 + row;
};
/**
* Retrieves a copy of the matrix column at the provided index as a Cartesian2 instance.
*
* @param {Matrix2} matrix The matrix to use.
* @param {Number} index The zero-based index of the column to retrieve.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*
* @exception {DeveloperError} index must be 0 or 1.
*/
Matrix2.getColumn = function(matrix, index, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (typeof index !== 'number' || index < 0 || index > 1) {
throw new DeveloperError('index must be 0 or 1.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var startIndex = index * 2;
var x = matrix[startIndex];
var y = matrix[startIndex + 1];
result.x = x;
result.y = y;
return result;
};
/**
* Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian2 instance.
*
* @param {Matrix2} matrix The matrix to use.
* @param {Number} index The zero-based index of the column to set.
* @param {Cartesian2} cartesian The Cartesian whose values will be assigned to the specified column.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*
* @exception {DeveloperError} index must be 0 or 1.
*/
Matrix2.setColumn = function(matrix, index, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof index !== 'number' || index < 0 || index > 1) {
throw new DeveloperError('index must be 0 or 1.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result = Matrix2.clone(matrix, result);
var startIndex = index * 2;
result[startIndex] = cartesian.x;
result[startIndex + 1] = cartesian.y;
return result;
};
/**
* Retrieves a copy of the matrix row at the provided index as a Cartesian2 instance.
*
* @param {Matrix2} matrix The matrix to use.
* @param {Number} index The zero-based index of the row to retrieve.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*
* @exception {DeveloperError} index must be 0 or 1.
*/
Matrix2.getRow = function(matrix, index, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (typeof index !== 'number' || index < 0 || index > 1) {
throw new DeveloperError('index must be 0 or 1.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var x = matrix[index];
var y = matrix[index + 2];
result.x = x;
result.y = y;
return result;
};
/**
* Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian2 instance.
*
* @param {Matrix2} matrix The matrix to use.
* @param {Number} index The zero-based index of the row to set.
* @param {Cartesian2} cartesian The Cartesian whose values will be assigned to the specified row.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*
* @exception {DeveloperError} index must be 0 or 1.
*/
Matrix2.setRow = function(matrix, index, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (typeof index !== 'number' || index < 0 || index > 1) {
throw new DeveloperError('index must be 0 or 1.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result = Matrix2.clone(matrix, result);
result[index] = cartesian.x;
result[index + 2] = cartesian.y;
return result;
};
var scratchColumn = new Cartesian2();
/**
* Extracts the non-uniform scale assuming the matrix is an affine transformation.
*
* @param {Matrix2} matrix The matrix.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Matrix2.getScale = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result.x = Cartesian2.magnitude(Cartesian2.fromElements(matrix[0], matrix[1], scratchColumn));
result.y = Cartesian2.magnitude(Cartesian2.fromElements(matrix[2], matrix[3], scratchColumn));
return result;
};
var scratchScale = new Cartesian2();
/**
* Computes the maximum scale assuming the matrix is an affine transformation.
* The maximum scale is the maximum length of the column vectors.
*
* @param {Matrix2} matrix The matrix.
* @returns {Number} The maximum scale.
*/
Matrix2.getMaximumScale = function(matrix) {
Matrix2.getScale(matrix, scratchScale);
return Cartesian2.maximumComponent(scratchScale);
};
/**
* Computes the product of two matrices.
*
* @param {Matrix2} left The first matrix.
* @param {Matrix2} right The second matrix.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.multiply = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var column0Row0 = left[0] * right[0] + left[2] * right[1];
var column1Row0 = left[0] * right[2] + left[2] * right[3];
var column0Row1 = left[1] * right[0] + left[3] * right[1];
var column1Row1 = left[1] * right[2] + left[3] * right[3];
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column1Row0;
result[3] = column1Row1;
return result;
};
/**
* Computes the sum of two matrices.
*
* @param {Matrix2} left The first matrix.
* @param {Matrix2} right The second matrix.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.add = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = left[0] + right[0];
result[1] = left[1] + right[1];
result[2] = left[2] + right[2];
result[3] = left[3] + right[3];
return result;
};
/**
* Computes the difference of two matrices.
*
* @param {Matrix2} left The first matrix.
* @param {Matrix2} right The second matrix.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.subtract = function(left, right, result) {
if (!defined(left)) {
throw new DeveloperError('left is required');
}
if (!defined(right)) {
throw new DeveloperError('right is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = left[0] - right[0];
result[1] = left[1] - right[1];
result[2] = left[2] - right[2];
result[3] = left[3] - right[3];
return result;
};
/**
* Computes the product of a matrix and a column vector.
*
* @param {Matrix2} matrix The matrix.
* @param {Cartesian2} cartesian The column.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter.
*/
Matrix2.multiplyByVector = function(matrix, cartesian, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(cartesian)) {
throw new DeveloperError('cartesian is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var x = matrix[0] * cartesian.x + matrix[2] * cartesian.y;
var y = matrix[1] * cartesian.x + matrix[3] * cartesian.y;
result.x = x;
result.y = y;
return result;
};
/**
* Computes the product of a matrix and a scalar.
*
* @param {Matrix2} matrix The matrix.
* @param {Number} scalar The number to multiply by.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.multiplyByScalar = function(matrix, scalar, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (typeof scalar !== 'number') {
throw new DeveloperError('scalar is required and must be a number');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0] * scalar;
result[1] = matrix[1] * scalar;
result[2] = matrix[2] * scalar;
result[3] = matrix[3] * scalar;
return result;
};
/**
* Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix.
*
* @param {Matrix2} matrix The matrix on the left-hand side.
* @param {Cartesian2} scale The non-uniform scale on the right-hand side.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*
*
* @example
* // Instead of Cesium.Matrix2.multiply(m, Cesium.Matrix2.fromScale(scale), m);
* Cesium.Matrix2.multiplyByScale(m, scale, m);
*
* @see Matrix2.fromScale
* @see Matrix2.multiplyByUniformScale
*/
Matrix2.multiplyByScale = function(matrix, scale, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(scale)) {
throw new DeveloperError('scale is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = matrix[0] * scale.x;
result[1] = matrix[1] * scale.x;
result[2] = matrix[2] * scale.y;
result[3] = matrix[3] * scale.y;
return result;
};
/**
* Creates a negated copy of the provided matrix.
*
* @param {Matrix2} matrix The matrix to negate.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.negate = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = -matrix[0];
result[1] = -matrix[1];
result[2] = -matrix[2];
result[3] = -matrix[3];
return result;
};
/**
* Computes the transpose of the provided matrix.
*
* @param {Matrix2} matrix The matrix to transpose.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.transpose = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
var column0Row0 = matrix[0];
var column0Row1 = matrix[2];
var column1Row0 = matrix[1];
var column1Row1 = matrix[3];
result[0] = column0Row0;
result[1] = column0Row1;
result[2] = column1Row0;
result[3] = column1Row1;
return result;
};
/**
* Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
*
* @param {Matrix2} matrix The matrix with signed elements.
* @param {Matrix2} result The object onto which to store the result.
* @returns {Matrix2} The modified result parameter.
*/
Matrix2.abs = function(matrix, result) {
if (!defined(matrix)) {
throw new DeveloperError('matrix is required');
}
if (!defined(result)) {
throw new DeveloperError('result is required');
}
result[0] = Math.abs(matrix[0]);
result[1] = Math.abs(matrix[1]);
result[2] = Math.abs(matrix[2]);
result[3] = Math.abs(matrix[3]);
return result;
};
/**
* Compares the provided matrices componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Matrix2} [left] The first matrix.
* @param {Matrix2} [right] The second matrix.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Matrix2.equals = function(left, right) {
return (left === right) ||
(defined(left) &&
defined(right) &&
left[0] === right[0] &&
left[1] === right[1] &&
left[2] === right[2] &&
left[3] === right[3]);
};
/**
* @private
*/
Matrix2.equalsArray = function(matrix, array, offset) {
return matrix[0] === array[offset] &&
matrix[1] === array[offset + 1] &&
matrix[2] === array[offset + 2] &&
matrix[3] === array[offset + 3];
};
/**
* Compares the provided matrices componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Matrix2} [left] The first matrix.
* @param {Matrix2} [right] The second matrix.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if left and right are within the provided epsilon, false
otherwise.
*/
Matrix2.equalsEpsilon = function(left, right, epsilon) {
if (typeof epsilon !== 'number') {
throw new DeveloperError('epsilon must be a number');
}
return (left === right) ||
(defined(left) &&
defined(right) &&
Math.abs(left[0] - right[0]) <= epsilon &&
Math.abs(left[1] - right[1]) <= epsilon &&
Math.abs(left[2] - right[2]) <= epsilon &&
Math.abs(left[3] - right[3]) <= epsilon);
};
/**
* An immutable Matrix2 instance initialized to the identity matrix.
*
* @type {Matrix2}
* @constant
*/
Matrix2.IDENTITY = freezeObject(new Matrix2(1.0, 0.0,
0.0, 1.0));
/**
* An immutable Matrix2 instance initialized to the zero matrix.
*
* @type {Matrix2}
* @constant
*/
Matrix2.ZERO = freezeObject(new Matrix2(0.0, 0.0,
0.0, 0.0));
/**
* The index into Matrix2 for column 0, row 0.
*
* @type {Number}
* @constant
*
* @example
* var matrix = new Cesium.Matrix2();
* matrix[Cesium.Matrix2.COLUMN0ROW0] = 5.0; // set column 0, row 0 to 5.0
*/
Matrix2.COLUMN0ROW0 = 0;
/**
* The index into Matrix2 for column 0, row 1.
*
* @type {Number}
* @constant
*
* @example
* var matrix = new Cesium.Matrix2();
* matrix[Cesium.Matrix2.COLUMN0ROW1] = 5.0; // set column 0, row 1 to 5.0
*/
Matrix2.COLUMN0ROW1 = 1;
/**
* The index into Matrix2 for column 1, row 0.
*
* @type {Number}
* @constant
*
* @example
* var matrix = new Cesium.Matrix2();
* matrix[Cesium.Matrix2.COLUMN1ROW0] = 5.0; // set column 1, row 0 to 5.0
*/
Matrix2.COLUMN1ROW0 = 2;
/**
* The index into Matrix2 for column 1, row 1.
*
* @type {Number}
* @constant
*
* @example
* var matrix = new Cesium.Matrix2();
* matrix[Cesium.Matrix2.COLUMN1ROW1] = 5.0; // set column 1, row 1 to 5.0
*/
Matrix2.COLUMN1ROW1 = 3;
defineProperties(Matrix2.prototype, {
/**
* Gets the number of items in the collection.
* @memberof Matrix2.prototype
*
* @type {Number}
*/
length : {
get : function() {
return Matrix2.packedLength;
}
}
});
/**
* Duplicates the provided Matrix2 instance.
*
* @param {Matrix2} [result] The object onto which to store the result.
* @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided.
*/
Matrix2.prototype.clone = function(result) {
return Matrix2.clone(this, result);
};
/**
* Compares this matrix to the provided matrix componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {Matrix2} [right] The right hand side matrix.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
Matrix2.prototype.equals = function(right) {
return Matrix2.equals(this, right);
};
/**
* Compares this matrix to the provided matrix componentwise and returns
* true
if they are within the provided epsilon,
* false
otherwise.
*
* @param {Matrix2} [right] The right hand side matrix.
* @param {Number} epsilon The epsilon to use for equality testing.
* @returns {Boolean} true
if they are within the provided epsilon, false
otherwise.
*/
Matrix2.prototype.equalsEpsilon = function(right, epsilon) {
return Matrix2.equalsEpsilon(this, right, epsilon);
};
/**
* Creates a string representing this Matrix with each row being
* on a separate line and in the format '(column0, column1)'.
*
* @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1)'.
*/
Matrix2.prototype.toString = function() {
return '(' + this[0] + ', ' + this[2] + ')\n' +
'(' + this[1] + ', ' + this[3] + ')';
};
return Matrix2;
});
/*global define*/
define('Core/mergeSort',[
'./defined',
'./DeveloperError'
], function(
defined,
DeveloperError) {
'use strict';
var leftScratchArray = [];
var rightScratchArray = [];
function merge(array, compare, userDefinedObject, start, middle, end) {
var leftLength = middle - start + 1;
var rightLength = end - middle;
var left = leftScratchArray;
var right = rightScratchArray;
var i;
var j;
for (i = 0; i < leftLength; ++i) {
left[i] = array[start + i];
}
for (j = 0; j < rightLength; ++j) {
right[j] = array[middle + j + 1];
}
i = 0;
j = 0;
for (var k = start; k <= end; ++k) {
var leftElement = left[i];
var rightElement = right[j];
if (i < leftLength && (j >= rightLength || compare(leftElement, rightElement, userDefinedObject) <= 0)) {
array[k] = leftElement;
++i;
} else if (j < rightLength) {
array[k] = rightElement;
++j;
}
}
}
function sort(array, compare, userDefinedObject, start, end) {
if (start >= end) {
return;
}
var middle = Math.floor((start + end) * 0.5);
sort(array, compare, userDefinedObject, start, middle);
sort(array, compare, userDefinedObject, middle + 1, end);
merge(array, compare, userDefinedObject, start, middle, end);
}
/**
* A stable merge sort.
*
* @exports mergeSort
*
* @param {Array} array The array to sort.
* @param {mergeSort~Comparator} comparator The function to use to compare elements in the array.
* @param {Object} [userDefinedObject] An object to pass as the third parameter to comparator
.
*
* @example
* // Assume array contains BoundingSpheres in world coordinates.
* // Sort them in ascending order of distance from the camera.
* var position = camera.positionWC;
* Cesium.mergeSort(array, function(a, b, position) {
* return Cesium.BoundingSphere.distanceSquaredTo(b, position) - Cesium.BoundingSphere.distanceSquaredTo(a, position);
* }, position);
*/
function mergeSort(array, comparator, userDefinedObject) {
if (!defined(array)) {
throw new DeveloperError('array is required.');
}
if (!defined(comparator)) {
throw new DeveloperError('comparator is required.');
}
var length = array.length;
var scratchLength = Math.ceil(length * 0.5);
// preallocate space in scratch arrays
leftScratchArray.length = scratchLength;
rightScratchArray.length = scratchLength;
sort(array, comparator, userDefinedObject, 0, length - 1);
// trim scratch arrays
leftScratchArray.length = 0;
rightScratchArray.length = 0;
}
/**
* A function used to compare two items while performing a merge sort.
* @callback mergeSort~Comparator
*
* @param {Object} a An item in the array.
* @param {Object} b An item in the array.
* @param {Object} [userDefinedObject] An object that was passed to {@link mergeSort}.
* @returns {Number} Returns a negative value if a
is less than b
,
* a positive value if a
is greater than b
, or
* 0 if a
is equal to b
.
*
* @example
* function compareNumbers(a, b, userDefinedObject) {
* return a - b;
* }
*/
return mergeSort;
});
/*global define*/
define('Core/NearFarScalar',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* Represents a scalar value's lower and upper bound at a near distance and far distance in eye space.
* @alias NearFarScalar
* @constructor
*
* @param {Number} [near=0.0] The lower bound of the camera range.
* @param {Number} [nearValue=0.0] The value at the lower bound of the camera range.
* @param {Number} [far=1.0] The upper bound of the camera range.
* @param {Number} [farValue=0.0] The value at the upper bound of the camera range.
*
* @see Packable
*/
function NearFarScalar(near, nearValue, far, farValue) {
/**
* The lower bound of the camera range.
* @type {Number}
* @default 0.0
*/
this.near = defaultValue(near, 0.0);
/**
* The value at the lower bound of the camera range.
* @type {Number}
* @default 0.0
*/
this.nearValue = defaultValue(nearValue, 0.0);
/**
* The upper bound of the camera range.
* @type {Number}
* @default 1.0
*/
this.far = defaultValue(far, 1.0);
/**
* The value at the upper bound of the camera range.
* @type {Number}
* @default 0.0
*/
this.farValue = defaultValue(farValue, 0.0);
}
/**
* Duplicates a NearFarScalar instance.
*
* @param {NearFarScalar} nearFarScalar The NearFarScalar to duplicate.
* @param {NearFarScalar} [result] The object onto which to store the result.
* @returns {NearFarScalar} The modified result parameter or a new NearFarScalar instance if one was not provided. (Returns undefined if nearFarScalar is undefined)
*/
NearFarScalar.clone = function(nearFarScalar, result) {
if (!defined(nearFarScalar)) {
return undefined;
}
if (!defined(result)) {
return new NearFarScalar(nearFarScalar.near, nearFarScalar.nearValue, nearFarScalar.far, nearFarScalar.farValue);
}
result.near = nearFarScalar.near;
result.nearValue = nearFarScalar.nearValue;
result.far = nearFarScalar.far;
result.farValue = nearFarScalar.farValue;
return result;
};
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
NearFarScalar.packedLength = 4;
/**
* Stores the provided instance into the provided array.
*
* @param {NearFarScalar} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
NearFarScalar.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex++] = value.near;
array[startingIndex++] = value.nearValue;
array[startingIndex++] = value.far;
array[startingIndex] = value.farValue;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {NearFarScalar} [result] The object into which to store the result.
* @returns {NearFarScalar} The modified result parameter or a new NearFarScalar instance if one was not provided.
*/
NearFarScalar.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
if (!defined(result)) {
result = new NearFarScalar();
}
result.near = array[startingIndex++];
result.nearValue = array[startingIndex++];
result.far = array[startingIndex++];
result.farValue = array[startingIndex];
return result;
};
/**
* Compares the provided NearFarScalar and returns true
if they are equal,
* false
otherwise.
*
* @param {NearFarScalar} [left] The first NearFarScalar.
* @param {NearFarScalar} [right] The second NearFarScalar.
* @returns {Boolean} true
if left and right are equal; otherwise false
.
*/
NearFarScalar.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.near === right.near) &&
(left.nearValue === right.nearValue) &&
(left.far === right.far) &&
(left.farValue === right.farValue));
};
/**
* Duplicates this instance.
*
* @param {NearFarScalar} [result] The object onto which to store the result.
* @returns {NearFarScalar} The modified result parameter or a new NearFarScalar instance if one was not provided.
*/
NearFarScalar.prototype.clone = function(result) {
return NearFarScalar.clone(this, result);
};
/**
* Compares this instance to the provided NearFarScalar and returns true
if they are equal,
* false
otherwise.
*
* @param {NearFarScalar} [right] The right hand side NearFarScalar.
* @returns {Boolean} true
if left and right are equal; otherwise false
.
*/
NearFarScalar.prototype.equals = function(right) {
return NearFarScalar.equals(this, right);
};
return NearFarScalar;
});
/*global define*/
define('Core/Visibility',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* This enumerated type is used in determining to what extent an object, the occludee,
* is visible during horizon culling. An occluder may fully block an occludee, in which case
* it has no visibility, may partially block an occludee from view, or may not block it at all,
* leading to full visibility.
*
* @exports Visibility
*/
var Visibility = {
/**
* Represents that no part of an object is visible.
*
* @type {Number}
* @constant
*/
NONE : -1,
/**
* Represents that part, but not all, of an object is visible
*
* @type {Number}
* @constant
*/
PARTIAL : 0,
/**
* Represents that an object is visible in its entirety.
*
* @type {Number}
* @constant
*/
FULL : 1
};
return freezeObject(Visibility);
});
/*global define*/
define('Core/Occluder',[
'./BoundingSphere',
'./Cartesian3',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Math',
'./Rectangle',
'./Visibility'
], function(
BoundingSphere,
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
CesiumMath,
Rectangle,
Visibility) {
'use strict';
/**
* Creates an Occluder derived from an object's position and radius, as well as the camera position.
* The occluder can be used to determine whether or not other objects are visible or hidden behind the
* visible horizon defined by the occluder and camera position.
*
* @alias Occluder
*
* @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
* @param {Cartesian3} cameraPosition The coordinate of the viewer/camera.
*
* @constructor
*
* @example
* // Construct an occluder one unit away from the origin with a radius of one.
* var cameraPosition = Cesium.Cartesian3.ZERO;
* var occluderBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 1);
* var occluder = new Cesium.Occluder(occluderBoundingSphere, cameraPosition);
*/
function Occluder(occluderBoundingSphere, cameraPosition) {
if (!defined(occluderBoundingSphere)) {
throw new DeveloperError('occluderBoundingSphere is required.');
}
if (!defined(cameraPosition)) {
throw new DeveloperError('camera position is required.');
}
this._occluderPosition = Cartesian3.clone(occluderBoundingSphere.center);
this._occluderRadius = occluderBoundingSphere.radius;
this._horizonDistance = 0.0;
this._horizonPlaneNormal = undefined;
this._horizonPlanePosition = undefined;
this._cameraPosition = undefined;
// cameraPosition fills in the above values
this.cameraPosition = cameraPosition;
}
var scratchCartesian3 = new Cartesian3();
defineProperties(Occluder.prototype, {
/**
* The position of the occluder.
* @memberof Occluder.prototype
* @type {Cartesian3}
*/
position: {
get: function() {
return this._occluderPosition;
}
},
/**
* The radius of the occluder.
* @memberof Occluder.prototype
* @type {Number}
*/
radius: {
get: function() {
return this._occluderRadius;
}
},
/**
* The position of the camera.
* @memberof Occluder.prototype
* @type {Cartesian3}
*/
cameraPosition: {
set: function(cameraPosition) {
if (!defined(cameraPosition)) {
throw new DeveloperError('cameraPosition is required.');
}
cameraPosition = Cartesian3.clone(cameraPosition, this._cameraPosition);
var cameraToOccluderVec = Cartesian3.subtract(this._occluderPosition, cameraPosition, scratchCartesian3);
var invCameraToOccluderDistance = Cartesian3.magnitudeSquared(cameraToOccluderVec);
var occluderRadiusSqrd = this._occluderRadius * this._occluderRadius;
var horizonDistance;
var horizonPlaneNormal;
var horizonPlanePosition;
if (invCameraToOccluderDistance > occluderRadiusSqrd) {
horizonDistance = Math.sqrt(invCameraToOccluderDistance - occluderRadiusSqrd);
invCameraToOccluderDistance = 1.0 / Math.sqrt(invCameraToOccluderDistance);
horizonPlaneNormal = Cartesian3.multiplyByScalar(cameraToOccluderVec, invCameraToOccluderDistance, scratchCartesian3);
var nearPlaneDistance = horizonDistance * horizonDistance * invCameraToOccluderDistance;
horizonPlanePosition = Cartesian3.add(cameraPosition, Cartesian3.multiplyByScalar(horizonPlaneNormal, nearPlaneDistance, scratchCartesian3), scratchCartesian3);
} else {
horizonDistance = Number.MAX_VALUE;
}
this._horizonDistance = horizonDistance;
this._horizonPlaneNormal = horizonPlaneNormal;
this._horizonPlanePosition = horizonPlanePosition;
this._cameraPosition = cameraPosition;
}
}
});
/**
* Creates an occluder from a bounding sphere and the camera position.
*
* @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
* @param {Cartesian3} cameraPosition The coordinate of the viewer/camera.
* @param {Occluder} [result] The object onto which to store the result.
* @returns {Occluder} The occluder derived from an object's position and radius, as well as the camera position.
*/
Occluder.fromBoundingSphere = function(occluderBoundingSphere, cameraPosition, result) {
if (!defined(occluderBoundingSphere)) {
throw new DeveloperError('occluderBoundingSphere is required.');
}
if (!defined(cameraPosition)) {
throw new DeveloperError('camera position is required.');
}
if (!defined(result)) {
return new Occluder(occluderBoundingSphere, cameraPosition);
}
Cartesian3.clone(occluderBoundingSphere.center, result._occluderPosition);
result._occluderRadius = occluderBoundingSphere.radius;
result.cameraPosition = cameraPosition;
return result;
};
var tempVecScratch = new Cartesian3();
/**
* Determines whether or not a point, the occludee
, is hidden from view by the occluder.
*
* @param {Cartesian3} occludee The point surrounding the occludee object.
* @returns {Boolean} true
if the occludee is visible; otherwise false
.
*
*
* @example
* var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
* var littleSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 0.25);
* var occluder = new Cesium.Occluder(littleSphere, cameraPosition);
* var point = new Cesium.Cartesian3(0, 0, -3);
* occluder.isPointVisible(point); //returns true
*
* @see Occluder#computeVisibility
*/
Occluder.prototype.isPointVisible = function(occludee) {
if (this._horizonDistance !== Number.MAX_VALUE) {
var tempVec = Cartesian3.subtract(occludee, this._occluderPosition, tempVecScratch);
var temp = this._occluderRadius;
temp = Cartesian3.magnitudeSquared(tempVec) - (temp * temp);
if (temp > 0.0) {
temp = Math.sqrt(temp) + this._horizonDistance;
tempVec = Cartesian3.subtract(occludee, this._cameraPosition, tempVec);
return temp * temp > Cartesian3.magnitudeSquared(tempVec);
}
}
return false;
};
var occludeePositionScratch = new Cartesian3();
/**
* Determines whether or not a sphere, the occludee
, is hidden from view by the occluder.
*
* @param {BoundingSphere} occludee The bounding sphere surrounding the occludee object.
* @returns {Boolean} true
if the occludee is visible; otherwise false
.
*
*
* @example
* var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
* var littleSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 0.25);
* var occluder = new Cesium.Occluder(littleSphere, cameraPosition);
* var bigSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -3), 1);
* occluder.isBoundingSphereVisible(bigSphere); //returns true
*
* @see Occluder#computeVisibility
*/
Occluder.prototype.isBoundingSphereVisible = function(occludee) {
var occludeePosition = Cartesian3.clone(occludee.center, occludeePositionScratch);
var occludeeRadius = occludee.radius;
if (this._horizonDistance !== Number.MAX_VALUE) {
var tempVec = Cartesian3.subtract(occludeePosition, this._occluderPosition, tempVecScratch);
var temp = this._occluderRadius - occludeeRadius;
temp = Cartesian3.magnitudeSquared(tempVec) - (temp * temp);
if (occludeeRadius < this._occluderRadius) {
if (temp > 0.0) {
temp = Math.sqrt(temp) + this._horizonDistance;
tempVec = Cartesian3.subtract(occludeePosition, this._cameraPosition, tempVec);
return ((temp * temp) + (occludeeRadius * occludeeRadius)) > Cartesian3.magnitudeSquared(tempVec);
}
return false;
}
// Prevent against the case where the occludee radius is larger than the occluder's; since this is
// an uncommon case, the following code should rarely execute.
if (temp > 0.0) {
tempVec = Cartesian3.subtract(occludeePosition, this._cameraPosition, tempVec);
var tempVecMagnitudeSquared = Cartesian3.magnitudeSquared(tempVec);
var occluderRadiusSquared = this._occluderRadius * this._occluderRadius;
var occludeeRadiusSquared = occludeeRadius * occludeeRadius;
if ((((this._horizonDistance * this._horizonDistance) + occluderRadiusSquared) * occludeeRadiusSquared) >
(tempVecMagnitudeSquared * occluderRadiusSquared)) {
// The occludee is close enough that the occluder cannot possible occlude the occludee
return true;
}
temp = Math.sqrt(temp) + this._horizonDistance;
return ((temp * temp) + occludeeRadiusSquared) > tempVecMagnitudeSquared;
}
// The occludee completely encompasses the occluder
return true;
}
return false;
};
var tempScratch = new Cartesian3();
/**
* Determine to what extent an occludee is visible (not visible, partially visible, or fully visible).
*
* @param {BoundingSphere} occludeeBS The bounding sphere of the occludee.
* @returns {Number} Visibility.NONE if the occludee is not visible,
* Visibility.PARTIAL if the occludee is partially visible, or
* Visibility.FULL if the occludee is fully visible.
*
*
* @example
* var sphere1 = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1.5), 0.5);
* var sphere2 = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -2.5), 0.5);
* var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
* var occluder = new Cesium.Occluder(sphere1, cameraPosition);
* occluder.computeVisibility(sphere2); //returns Visibility.NONE
*
* @see Occluder#isVisible
*/
Occluder.prototype.computeVisibility = function(occludeeBS) {
if (!defined(occludeeBS)) {
throw new DeveloperError('occludeeBS is required.');
}
// If the occludee radius is larger than the occluders, this will return that
// the entire ocludee is visible, even though that may not be the case, though this should
// not occur too often.
var occludeePosition = Cartesian3.clone(occludeeBS.center);
var occludeeRadius = occludeeBS.radius;
if (occludeeRadius > this._occluderRadius) {
return Visibility.FULL;
}
if (this._horizonDistance !== Number.MAX_VALUE) {
// The camera is outside the occluder
var tempVec = Cartesian3.subtract(occludeePosition, this._occluderPosition, tempScratch);
var temp = this._occluderRadius - occludeeRadius;
var occluderToOccludeeDistSqrd = Cartesian3.magnitudeSquared(tempVec);
temp = occluderToOccludeeDistSqrd - (temp * temp);
if (temp > 0.0) {
// The occludee is not completely inside the occluder
// Check to see if the occluder completely hides the occludee
temp = Math.sqrt(temp) + this._horizonDistance;
tempVec = Cartesian3.subtract(occludeePosition, this._cameraPosition, tempVec);
var cameraToOccludeeDistSqrd = Cartesian3.magnitudeSquared(tempVec);
if (((temp * temp) + (occludeeRadius * occludeeRadius)) < cameraToOccludeeDistSqrd) {
return Visibility.NONE;
}
// Check to see whether the occluder is fully or partially visible
// when the occludee does not intersect the occluder
temp = this._occluderRadius + occludeeRadius;
temp = occluderToOccludeeDistSqrd - (temp * temp);
if (temp > 0.0) {
// The occludee does not intersect the occluder.
temp = Math.sqrt(temp) + this._horizonDistance;
return (cameraToOccludeeDistSqrd < ((temp * temp)) + (occludeeRadius * occludeeRadius)) ? Visibility.FULL : Visibility.PARTIAL;
}
//Check to see if the occluder is fully or partially visible when the occludee DOES
//intersect the occluder
tempVec = Cartesian3.subtract(occludeePosition, this._horizonPlanePosition, tempVec);
return (Cartesian3.dot(tempVec, this._horizonPlaneNormal) > -occludeeRadius) ? Visibility.PARTIAL : Visibility.FULL;
}
}
return Visibility.NONE;
};
var occludeePointScratch = new Cartesian3();
/**
* Computes a point that can be used as the occludee position to the visibility functions.
* Use a radius of zero for the occludee radius. Typically, a user computes a bounding sphere around
* an object that is used for visibility; however it is also possible to compute a point that if
* seen/not seen would also indicate if an object is visible/not visible. This function is better
* called for objects that do not move relative to the occluder and is large, such as a chunk of
* terrain. You are better off not calling this and using the object's bounding sphere for objects
* such as a satellite or ground vehicle.
*
* @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
* @param {Cartesian3} occludeePosition The point where the occludee (bounding sphere of radius 0) is located.
* @param {Cartesian3[]} positions List of altitude points on the horizon near the surface of the occluder.
* @returns {Object} An object containing two attributes: occludeePoint
and valid
* which is a boolean value.
*
* @exception {DeveloperError} positions
must contain at least one element.
* @exception {DeveloperError} occludeePosition
must have a value other than occluderBoundingSphere.center
.
*
* @example
* var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
* var occluderBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -8), 2);
* var occluder = new Cesium.Occluder(occluderBoundingSphere, cameraPosition);
* var positions = [new Cesium.Cartesian3(-0.25, 0, -5.3), new Cesium.Cartesian3(0.25, 0, -5.3)];
* var tileOccluderSphere = Cesium.BoundingSphere.fromPoints(positions);
* var occludeePosition = tileOccluderSphere.center;
* var occludeePt = Cesium.Occluder.computeOccludeePoint(occluderBoundingSphere, occludeePosition, positions);
*/
Occluder.computeOccludeePoint = function(occluderBoundingSphere, occludeePosition, positions) {
if (!defined(occluderBoundingSphere)) {
throw new DeveloperError('occluderBoundingSphere is required.');
}
if (!defined(positions)) {
throw new DeveloperError('positions is required.');
}
if (positions.length === 0) {
throw new DeveloperError('positions must contain at least one element');
}
var occludeePos = Cartesian3.clone(occludeePosition);
var occluderPosition = Cartesian3.clone(occluderBoundingSphere.center);
var occluderRadius = occluderBoundingSphere.radius;
var numPositions = positions.length;
if (Cartesian3.equals(occluderPosition, occludeePosition)) {
throw new DeveloperError('occludeePosition must be different than occluderBoundingSphere.center');
}
// Compute a plane with a normal from the occluder to the occludee position.
var occluderPlaneNormal = Cartesian3.normalize(Cartesian3.subtract(occludeePos, occluderPosition, occludeePointScratch), occludeePointScratch);
var occluderPlaneD = -(Cartesian3.dot(occluderPlaneNormal, occluderPosition));
//For each position, determine the horizon intersection. Choose the position and intersection
//that results in the greatest angle with the occcluder plane.
var aRotationVector = Occluder._anyRotationVector(occluderPosition, occluderPlaneNormal, occluderPlaneD);
var dot = Occluder._horizonToPlaneNormalDotProduct(occluderBoundingSphere, occluderPlaneNormal, occluderPlaneD, aRotationVector, positions[0]);
if (!dot) {
//The position is inside the mimimum radius, which is invalid
return undefined;
}
var tempDot;
for ( var i = 1; i < numPositions; ++i) {
tempDot = Occluder._horizonToPlaneNormalDotProduct(occluderBoundingSphere, occluderPlaneNormal, occluderPlaneD, aRotationVector, positions[i]);
if (!tempDot) {
//The position is inside the minimum radius, which is invalid
return undefined;
}
if (tempDot < dot) {
dot = tempDot;
}
}
//Verify that the dot is not near 90 degress
if (dot < 0.00174532836589830883577820272085) {
return undefined;
}
var distance = occluderRadius / dot;
return Cartesian3.add(occluderPosition, Cartesian3.multiplyByScalar(occluderPlaneNormal, distance, occludeePointScratch), occludeePointScratch);
};
var computeOccludeePointFromRectangleScratch = [];
/**
* Computes a point that can be used as the occludee position to the visibility functions from an rectangle.
*
* @param {Rectangle} rectangle The rectangle used to create a bounding sphere.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the rectangle.
* @returns {Object} An object containing two attributes: occludeePoint
and valid
* which is a boolean value.
*/
Occluder.computeOccludeePointFromRectangle = function(rectangle, ellipsoid) {
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var positions = Rectangle.subsample(rectangle, ellipsoid, 0.0, computeOccludeePointFromRectangleScratch);
var bs = BoundingSphere.fromPoints(positions);
// TODO: get correct ellipsoid center
var ellipsoidCenter = Cartesian3.ZERO;
if (!Cartesian3.equals(ellipsoidCenter, bs.center)) {
return Occluder.computeOccludeePoint(new BoundingSphere(ellipsoidCenter, ellipsoid.minimumRadius), bs.center, positions);
}
return undefined;
};
var tempVec0Scratch = new Cartesian3();
Occluder._anyRotationVector = function(occluderPosition, occluderPlaneNormal, occluderPlaneD) {
var tempVec0 = Cartesian3.abs(occluderPlaneNormal, tempVec0Scratch);
var majorAxis = tempVec0.x > tempVec0.y ? 0 : 1;
if (((majorAxis === 0) && (tempVec0.z > tempVec0.x)) || ((majorAxis === 1) && (tempVec0.z > tempVec0.y))) {
majorAxis = 2;
}
var tempVec = new Cartesian3();
var tempVec1;
if (majorAxis === 0) {
tempVec0.x = occluderPosition.x;
tempVec0.y = occluderPosition.y + 1.0;
tempVec0.z = occluderPosition.z + 1.0;
tempVec1 = Cartesian3.UNIT_X;
} else if (majorAxis === 1) {
tempVec0.x = occluderPosition.x + 1.0;
tempVec0.y = occluderPosition.y;
tempVec0.z = occluderPosition.z + 1.0;
tempVec1 = Cartesian3.UNIT_Y;
} else {
tempVec0.x = occluderPosition.x + 1.0;
tempVec0.y = occluderPosition.y + 1.0;
tempVec0.z = occluderPosition.z;
tempVec1 = Cartesian3.UNIT_Z;
}
var u = (Cartesian3.dot(occluderPlaneNormal, tempVec0) + occluderPlaneD) / -(Cartesian3.dot(occluderPlaneNormal, tempVec1));
return Cartesian3.normalize(Cartesian3.subtract(Cartesian3.add(tempVec0, Cartesian3.multiplyByScalar(tempVec1, u, tempVec), tempVec0), occluderPosition, tempVec0), tempVec0);
};
var posDirectionScratch = new Cartesian3();
Occluder._rotationVector = function(occluderPosition, occluderPlaneNormal, occluderPlaneD, position, anyRotationVector) {
//Determine the angle between the occluder plane normal and the position direction
var positionDirection = Cartesian3.subtract(position, occluderPosition, posDirectionScratch);
positionDirection = Cartesian3.normalize(positionDirection, positionDirection);
if (Cartesian3.dot(occluderPlaneNormal, positionDirection) < 0.99999998476912904932780850903444) {
var crossProduct = Cartesian3.cross(occluderPlaneNormal, positionDirection, positionDirection);
var length = Cartesian3.magnitude(crossProduct);
if (length > CesiumMath.EPSILON13) {
return Cartesian3.normalize(crossProduct, new Cartesian3());
}
}
//The occluder plane normal and the position direction are colinear. Use any
//vector in the occluder plane as the rotation vector
return anyRotationVector;
};
var posScratch1 = new Cartesian3();
var occluerPosScratch = new Cartesian3();
var posScratch2 = new Cartesian3();
var horizonPlanePosScratch = new Cartesian3();
Occluder._horizonToPlaneNormalDotProduct = function(occluderBS, occluderPlaneNormal, occluderPlaneD, anyRotationVector, position) {
var pos = Cartesian3.clone(position, posScratch1);
var occluderPosition = Cartesian3.clone(occluderBS.center, occluerPosScratch);
var occluderRadius = occluderBS.radius;
//Verify that the position is outside the occluder
var positionToOccluder = Cartesian3.subtract(occluderPosition, pos, posScratch2);
var occluderToPositionDistanceSquared = Cartesian3.magnitudeSquared(positionToOccluder);
var occluderRadiusSquared = occluderRadius * occluderRadius;
if (occluderToPositionDistanceSquared < occluderRadiusSquared) {
return false;
}
//Horizon parameters
var horizonDistanceSquared = occluderToPositionDistanceSquared - occluderRadiusSquared;
var horizonDistance = Math.sqrt(horizonDistanceSquared);
var occluderToPositionDistance = Math.sqrt(occluderToPositionDistanceSquared);
var invOccluderToPositionDistance = 1.0 / occluderToPositionDistance;
var cosTheta = horizonDistance * invOccluderToPositionDistance;
var horizonPlaneDistance = cosTheta * horizonDistance;
positionToOccluder = Cartesian3.normalize(positionToOccluder, positionToOccluder);
var horizonPlanePosition = Cartesian3.add(pos, Cartesian3.multiplyByScalar(positionToOccluder, horizonPlaneDistance, horizonPlanePosScratch), horizonPlanePosScratch);
var horizonCrossDistance = Math.sqrt(horizonDistanceSquared - (horizonPlaneDistance * horizonPlaneDistance));
//Rotate the position to occluder vector 90 degrees
var tempVec = this._rotationVector(occluderPosition, occluderPlaneNormal, occluderPlaneD, pos, anyRotationVector);
var horizonCrossDirection = Cartesian3.fromElements(
(tempVec.x * tempVec.x * positionToOccluder.x) + ((tempVec.x * tempVec.y - tempVec.z) * positionToOccluder.y) + ((tempVec.x * tempVec.z + tempVec.y) * positionToOccluder.z),
((tempVec.x * tempVec.y + tempVec.z) * positionToOccluder.x) + (tempVec.y * tempVec.y * positionToOccluder.y) + ((tempVec.y * tempVec.z - tempVec.x) * positionToOccluder.z),
((tempVec.x * tempVec.z - tempVec.y) * positionToOccluder.x) + ((tempVec.y * tempVec.z + tempVec.x) * positionToOccluder.y) + (tempVec.z * tempVec.z * positionToOccluder.z),
posScratch1);
horizonCrossDirection = Cartesian3.normalize(horizonCrossDirection, horizonCrossDirection);
//Horizon positions
var offset = Cartesian3.multiplyByScalar(horizonCrossDirection, horizonCrossDistance, posScratch1);
tempVec = Cartesian3.normalize(Cartesian3.subtract(Cartesian3.add(horizonPlanePosition, offset, posScratch2), occluderPosition, posScratch2), posScratch2);
var dot0 = Cartesian3.dot(occluderPlaneNormal, tempVec);
tempVec = Cartesian3.normalize(Cartesian3.subtract(Cartesian3.subtract(horizonPlanePosition, offset, tempVec), occluderPosition, tempVec), tempVec);
var dot1 = Cartesian3.dot(occluderPlaneNormal, tempVec);
return (dot0 < dot1) ? dot0 : dot1;
};
return Occluder;
});
/*global define*/
define('Core/Packable',[
'./DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* Static interface for types which can store their values as packed
* elements in an array. These methods and properties are expected to be
* defined on a constructor function.
*
* @exports Packable
*
* @see PackableForInterpolation
*/
var Packable = {
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
packedLength : undefined,
/**
* Stores the provided instance into the provided array.
* @function
*
* @param {Object} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*/
pack : DeveloperError.throwInstantiationError,
/**
* Retrieves an instance from a packed array.
* @function
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Object} [result] The object into which to store the result.
* @returns {Object} The modified result parameter or a new Object instance if one was not provided.
*/
unpack : DeveloperError.throwInstantiationError
};
return Packable;
});
/*global define*/
define('Core/PackableForInterpolation',[
'./DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* Static interface for {@link Packable} types which are interpolated in a
* different representation than their packed value. These methods and
* properties are expected to be defined on a constructor function.
*
* @exports PackableForInterpolation
*
* @see Packable
*/
var PackableForInterpolation = {
/**
* The number of elements used to store the object into an array in its interpolatable form.
* @type {Number}
*/
packedInterpolationLength : undefined,
/**
* Converts a packed array into a form suitable for interpolation.
* @function
*
* @param {Number[]} packedArray The packed array.
* @param {Number} [startingIndex=0] The index of the first element to be converted.
* @param {Number} [lastIndex=packedArray.length] The index of the last element to be converted.
* @param {Number[]} result The object into which to store the result.
*/
convertPackedArrayForInterpolation : DeveloperError.throwInstantiationError,
/**
* Retrieves an instance from a packed array converted with {@link PackableForInterpolation.convertPackedArrayForInterpolation}.
* @function
*
* @param {Number[]} array The array previously packed for interpolation.
* @param {Number[]} sourceArray The original packed array.
* @param {Number} [startingIndex=0] The startingIndex used to convert the array.
* @param {Number} [lastIndex=packedArray.length] The lastIndex used to convert the array.
* @param {Object} [result] The object into which to store the result.
* @returns {Object} The modified result parameter or a new Object instance if one was not provided.
*/
unpackInterpolationResult : DeveloperError.throwInstantiationError
};
return PackableForInterpolation;
});
/*
This library rewrites the Canvas2D "measureText" function
so that it returns a more complete metrics object.
** -----------------------------------------------------------------------------
CHANGELOG:
2012-01-21 - Whitespace handling added by Joe Turner
(https://github.com/oampo)
** -----------------------------------------------------------------------------
*/
/**
@license
fontmetrics.js - https://github.com/Pomax/fontmetrics.js
Copyright (C) 2011 by Mike "Pomax" Kamermans
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
**/
/*global define*/
define('ThirdParty/measureText',[],function() {
/*jshint strict:false*/
/*
var NAME = "FontMetrics Library"
var VERSION = "1-2012.0121.1300";
// if there is no getComputedStyle, this library won't work.
if(!document.defaultView.getComputedStyle) {
throw("ERROR: 'document.defaultView.getComputedStyle' not found. This library only works in browsers that can report computed CSS values.");
}
// store the old text metrics function on the Canvas2D prototype
CanvasRenderingContext2D.prototype.measureTextWidth = CanvasRenderingContext2D.prototype.measureText;
*/
/**
* shortcut function for getting computed CSS values
*/
var getCSSValue = function(element, property) {
return document.defaultView.getComputedStyle(element,null).getPropertyValue(property);
};
/*
// debug function
var show = function(canvas, ctx, xstart, w, h, metrics)
{
document.body.appendChild(canvas);
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.beginPath();
ctx.moveTo(xstart,0);
ctx.lineTo(xstart,h);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(xstart+metrics.bounds.maxx,0);
ctx.lineTo(xstart+metrics.bounds.maxx,h);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0,h/2-metrics.ascent);
ctx.lineTo(w,h/2-metrics.ascent);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0,h/2+metrics.descent);
ctx.lineTo(w,h/2+metrics.descent);
ctx.closePath();
ctx.stroke();
}
*/
/**
* The new text metrics function
*/
var measureText = function(context2D, textstring, stroke, fill) {
var metrics = context2D.measureText(textstring),
fontFamily = getCSSValue(context2D.canvas,"font-family"),
fontSize = getCSSValue(context2D.canvas,"font-size").replace("px",""),
fontStyle = getCSSValue(context2D.canvas,"font-style"),
fontWeight = getCSSValue(context2D.canvas,"font-weight"),
isSpace = !(/\S/.test(textstring));
metrics.fontsize = fontSize;
// for text lead values, we meaure a multiline text container.
var leadDiv = document.createElement("div");
leadDiv.style.position = "absolute";
leadDiv.style.opacity = 0;
leadDiv.style.font = fontStyle + " " + fontWeight + " " + fontSize + "px " + fontFamily;
leadDiv.innerHTML = textstring + " " + textstring;
document.body.appendChild(leadDiv);
// make some initial guess at the text leading (using the standard TeX ratio)
metrics.leading = 1.2 * fontSize;
// then we try to get the real value from the browser
var leadDivHeight = getCSSValue(leadDiv,"height");
leadDivHeight = leadDivHeight.replace("px","");
if (leadDivHeight >= fontSize * 2) { metrics.leading = (leadDivHeight/2) | 0; }
document.body.removeChild(leadDiv);
// if we're not dealing with white space, we can compute metrics
if (!isSpace) {
// Have characters, so measure the text
var canvas = document.createElement("canvas");
var padding = 100;
canvas.width = metrics.width + padding;
canvas.height = 3*fontSize;
canvas.style.opacity = 1;
canvas.style.fontFamily = fontFamily;
canvas.style.fontSize = fontSize;
canvas.style.fontStyle = fontStyle;
canvas.style.fontWeight = fontWeight;
var ctx = canvas.getContext("2d");
ctx.font = fontStyle + " " + fontWeight + " " + fontSize + "px " + fontFamily;
var w = canvas.width,
h = canvas.height,
baseline = h/2;
// Set all canvas pixeldata values to 255, with all the content
// data being 0. This lets us scan for data[i] != 255.
ctx.fillStyle = "white";
ctx.fillRect(-1, -1, w + 2, h + 2);
if (stroke) {
ctx.strokeStyle = "black";
ctx.lineWidth = context2D.lineWidth;
ctx.strokeText(textstring, (padding / 2), baseline);
}
if (fill) {
ctx.fillStyle = "black";
ctx.fillText(textstring, padding / 2, baseline);
}
var pixelData = ctx.getImageData(0, 0, w, h).data;
// canvas pixel data is w*4 by h*4, because R, G, B and A are separate,
// consecutive values in the array, rather than stored as 32 bit ints.
var i = 0,
w4 = w * 4,
len = pixelData.length;
// Finding the ascent uses a normal, forward scanline
while (++i < len && pixelData[i] === 255) {}
var ascent = (i/w4)|0;
// Finding the descent uses a reverse scanline
i = len - 1;
while (--i > 0 && pixelData[i] === 255) {}
var descent = (i/w4)|0;
// find the min-x coordinate
for(i = 0; i=len) { i = (i-len) + 4; }}
var minx = ((i%w4)/4) | 0;
// find the max-x coordinate
var step = 1;
for(i = len-3; i>=0 && pixelData[i] === 255; ) {
i -= w4;
if(i<0) { i = (len - 3) - (step++)*4; }}
var maxx = ((i%w4)/4) + 1 | 0;
// set font metrics
metrics.ascent = (baseline - ascent);
metrics.descent = (descent - baseline);
metrics.bounds = { minx: minx - (padding/2),
maxx: maxx - (padding/2),
miny: 0,
maxy: descent-ascent };
metrics.height = 1+(descent - ascent);
}
// if we ARE dealing with whitespace, most values will just be zero.
else {
// Only whitespace, so we can't measure the text
metrics.ascent = 0;
metrics.descent = 0;
metrics.bounds = { minx: 0,
maxx: metrics.width, // Best guess
miny: 0,
maxy: 0 };
metrics.height = 0;
}
return metrics;
};
return measureText;
});
/*global define*/
define('Core/writeTextToCanvas',[
'../ThirdParty/measureText',
'./Color',
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
measureText,
Color,
defaultValue,
defined,
DeveloperError) {
'use strict';
var imageSmoothingEnabledName;
/**
* Writes the given text into a new canvas. The canvas will be sized to fit the text.
* If text is blank, returns undefined.
*
* @param {String} text The text to write.
* @param {Object} [options] Object with the following properties:
* @param {String} [options.font='10px sans-serif'] The CSS font to use.
* @param {String} [options.textBaseline='bottom'] The baseline of the text.
* @param {Boolean} [options.fill=true] Whether to fill the text.
* @param {Boolean} [options.stroke=false] Whether to stroke the text.
* @param {Color} [options.fillColor=Color.WHITE] The fill color.
* @param {Color} [options.strokeColor=Color.BLACK] The stroke color.
* @param {Number} [options.strokeWidth=1] The stroke width.
* @param {Color} [options.backgroundColor=Color.TRANSPARENT] The background color of the canvas.
* @param {Number} [options.padding=0] The pixel size of the padding to add around the text.
* @returns {Canvas} A new canvas with the given text drawn into it. The dimensions object
* from measureText will also be added to the returned canvas. If text is
* blank, returns undefined.
*/
function writeTextToCanvas(text, options) {
if (!defined(text)) {
throw new DeveloperError('text is required.');
}
if (text === '') {
return undefined;
}
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var font = defaultValue(options.font, '10px sans-serif');
var stroke = defaultValue(options.stroke, false);
var fill = defaultValue(options.fill, true);
var strokeWidth = defaultValue(options.strokeWidth, 1);
var backgroundColor = defaultValue(options.backgroundColor, Color.TRANSPARENT);
var padding = defaultValue(options.padding, 0);
var doublePadding = padding * 2.0;
var canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
canvas.style.font = font;
var context2D = canvas.getContext('2d');
if (!defined(imageSmoothingEnabledName)) {
if (defined(context2D.imageSmoothingEnabled)) {
imageSmoothingEnabledName = 'imageSmoothingEnabled';
} else if (defined(context2D.mozImageSmoothingEnabled)) {
imageSmoothingEnabledName = 'mozImageSmoothingEnabled';
} else if (defined(context2D.webkitImageSmoothingEnabled)) {
imageSmoothingEnabledName = 'webkitImageSmoothingEnabled';
} else if (defined(context2D.msImageSmoothingEnabled)) {
imageSmoothingEnabledName = 'msImageSmoothingEnabled';
}
}
context2D.font = font;
context2D.lineJoin = 'round';
context2D.lineWidth = strokeWidth;
context2D[imageSmoothingEnabledName] = false;
// textBaseline needs to be set before the measureText call. It won't work otherwise.
// It's magic.
context2D.textBaseline = defaultValue(options.textBaseline, 'bottom');
// in order for measureText to calculate style, the canvas has to be
// (temporarily) added to the DOM.
canvas.style.visibility = 'hidden';
document.body.appendChild(canvas);
var dimensions = measureText(context2D, text, stroke, fill);
canvas.dimensions = dimensions;
document.body.removeChild(canvas);
canvas.style.visibility = '';
//Some characters, such as the letter j, have a non-zero starting position.
//This value is used for kerning later, but we need to take it into account
//now in order to draw the text completely on the canvas
var x = -dimensions.bounds.minx;
//Expand the width to include the starting position.
var width = Math.ceil(dimensions.width) + x + doublePadding;
//While the height of the letter is correct, we need to adjust
//where we start drawing it so that letters like j and y properly dip
//below the line.
var height = dimensions.height + doublePadding;
var baseline = height - dimensions.ascent + doublePadding;
var y = height - baseline + doublePadding;
canvas.width = width;
canvas.height = height;
// Properties must be explicitly set again after changing width and height
context2D.font = font;
context2D.lineJoin = 'round';
context2D.lineWidth = strokeWidth;
context2D[imageSmoothingEnabledName] = false;
// Draw background
if (backgroundColor !== Color.TRANSPARENT) {
context2D.fillStyle = backgroundColor.toCssColorString();
context2D.fillRect(0, 0, canvas.width, canvas.height);
}
if (stroke) {
var strokeColor = defaultValue(options.strokeColor, Color.BLACK);
context2D.strokeStyle = strokeColor.toCssColorString();
context2D.strokeText(text, x + padding, y);
}
if (fill) {
var fillColor = defaultValue(options.fillColor, Color.WHITE);
context2D.fillStyle = fillColor.toCssColorString();
context2D.fillText(text, x + padding, y);
}
return canvas;
}
return writeTextToCanvas;
});
/*global define*/
define('Core/PinBuilder',[
'./buildModuleUrl',
'./Color',
'./defined',
'./DeveloperError',
'./loadImage',
'./writeTextToCanvas'
], function(
buildModuleUrl,
Color,
defined,
DeveloperError,
loadImage,
writeTextToCanvas) {
'use strict';
/**
* A utility class for generating custom map pins as canvas elements.
*
*
*
* Example pins generated using both the maki icon set, which ships with Cesium, and single character text.
*
*
* @alias PinBuilder
* @constructor
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Map%20Pins.html|Cesium Sandcastle PinBuilder Demo}
*/
function PinBuilder() {
this._cache = {};
}
/**
* Creates an empty pin of the specified color and size.
*
* @param {Color} color The color of the pin.
* @param {Number} size The size of the pin, in pixels.
* @returns {Canvas} The canvas element that represents the generated pin.
*/
PinBuilder.prototype.fromColor = function(color, size) {
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(size)) {
throw new DeveloperError('size is required');
}
return createPin(undefined, undefined, color, size, this._cache);
};
/**
* Creates a pin with the specified icon, color, and size.
*
* @param {String} url The url of the image to be stamped onto the pin.
* @param {Color} color The color of the pin.
* @param {Number} size The size of the pin, in pixels.
* @returns {Canvas|Promise.} The canvas element or a Promise to the canvas element that represents the generated pin.
*/
PinBuilder.prototype.fromUrl = function(url, color, size) {
if (!defined(url)) {
throw new DeveloperError('url is required');
}
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(size)) {
throw new DeveloperError('size is required');
}
return createPin(url, undefined, color, size, this._cache);
};
/**
* Creates a pin with the specified {@link https://www.mapbox.com/maki/|maki} icon identifier, color, and size.
*
* @param {String} id The id of the maki icon to be stamped onto the pin.
* @param {Color} color The color of the pin.
* @param {Number} size The size of the pin, in pixels.
* @returns {Canvas|Promise.} The canvas element or a Promise to the canvas element that represents the generated pin.
*/
PinBuilder.prototype.fromMakiIconId = function(id, color, size) {
if (!defined(id)) {
throw new DeveloperError('id is required');
}
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(size)) {
throw new DeveloperError('size is required');
}
return createPin(buildModuleUrl('Assets/Textures/maki/' + encodeURIComponent(id) + '.png'), undefined, color, size, this._cache);
};
/**
* Creates a pin with the specified text, color, and size. The text will be sized to be as large as possible
* while still being contained completely within the pin.
*
* @param {String} text The text to be stamped onto the pin.
* @param {Color} color The color of the pin.
* @param {Number} size The size of the pin, in pixels.
* @returns {Canvas} The canvas element that represents the generated pin.
*/
PinBuilder.prototype.fromText = function(text, color, size) {
if (!defined(text)) {
throw new DeveloperError('text is required');
}
if (!defined(color)) {
throw new DeveloperError('color is required');
}
if (!defined(size)) {
throw new DeveloperError('size is required');
}
return createPin(undefined, text, color, size, this._cache);
};
var colorScratch = new Color();
//This function (except for the 3 commented lines) was auto-generated from an online tool,
//http://www.professorcloud.com/svg-to-canvas/, using Assets/Textures/pin.svg as input.
//The reason we simply can't load and draw the SVG directly to the canvas is because
//it taints the canvas in Internet Explorer (and possibly some other browsers); making
//it impossible to create a WebGL texture from the result.
function drawPin(context2D, color, size) {
context2D.save();
context2D.scale(size / 24, size / 24); //Added to auto-generated code to scale up to desired size.
context2D.fillStyle = color.toCssColorString(); //Modified from auto-generated code.
context2D.strokeStyle = color.brighten(0.6, colorScratch).toCssColorString(); //Modified from auto-generated code.
context2D.lineWidth = 0.846;
context2D.beginPath();
context2D.moveTo(6.72, 0.422);
context2D.lineTo(17.28, 0.422);
context2D.bezierCurveTo(18.553, 0.422, 19.577, 1.758, 19.577, 3.415);
context2D.lineTo(19.577, 10.973);
context2D.bezierCurveTo(19.577, 12.63, 18.553, 13.966, 17.282, 13.966);
context2D.lineTo(14.386, 14.008);
context2D.lineTo(11.826, 23.578);
context2D.lineTo(9.614, 14.008);
context2D.lineTo(6.719, 13.965);
context2D.bezierCurveTo(5.446, 13.983, 4.422, 12.629, 4.422, 10.972);
context2D.lineTo(4.422, 3.416);
context2D.bezierCurveTo(4.423, 1.76, 5.447, 0.423, 6.718, 0.423);
context2D.closePath();
context2D.fill();
context2D.stroke();
context2D.restore();
}
//This function takes an image or canvas and uses it as a template
//to "stamp" the pin with a white image outlined in black. The color
//values of the input image are ignored completely and only the alpha
//values are used.
function drawIcon(context2D, image, size) {
//Size is the largest image that looks good inside of pin box.
var imageSize = size / 2.5;
var sizeX = imageSize;
var sizeY = imageSize;
if (image.width > image.height) {
sizeY = imageSize * (image.height / image.width);
} else if (image.width < image.height) {
sizeX = imageSize * (image.width / image.height);
}
//x and y are the center of the pin box
var x = (size - sizeX) / 2;
var y = ((7 / 24) * size) - (sizeY / 2);
context2D.globalCompositeOperation = 'destination-out';
context2D.drawImage(image, x - 1, y, sizeX, sizeY);
context2D.drawImage(image, x, y - 1, sizeX, sizeY);
context2D.drawImage(image, x + 1, y, sizeX, sizeY);
context2D.drawImage(image, x, y + 1, sizeX, sizeY);
context2D.globalCompositeOperation = 'destination-over';
context2D.fillStyle = Color.BLACK.toCssColorString();
context2D.fillRect(x - 1, y - 1, sizeX + 1, sizeY + 1);
context2D.globalCompositeOperation = 'destination-out';
context2D.drawImage(image, x, y, sizeX, sizeY);
context2D.globalCompositeOperation = 'destination-over';
context2D.fillStyle = Color.WHITE.toCssColorString();
context2D.fillRect(x, y, sizeX, sizeY);
}
var stringifyScratch = new Array(4);
function createPin(url, label, color, size, cache) {
//Use the parameters as a unique ID for caching.
stringifyScratch[0] = url;
stringifyScratch[1] = label;
stringifyScratch[2] = color;
stringifyScratch[3] = size;
var id = JSON.stringify(stringifyScratch);
var item = cache[id];
if (defined(item)) {
return item;
}
var canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
var context2D = canvas.getContext("2d");
drawPin(context2D, color, size);
if (defined(url)) {
//If we have an image url, load it and then stamp the pin.
var promise = loadImage(url).then(function(image) {
drawIcon(context2D, image, size);
cache[id] = canvas;
return canvas;
});
cache[id] = promise;
return promise;
} else if (defined(label)) {
//If we have a label, write it to a canvas and then stamp the pin.
var image = writeTextToCanvas(label, {
font : 'bold ' + size + 'px sans-serif'
});
drawIcon(context2D, image, size);
}
cache[id] = canvas;
return canvas;
}
return PinBuilder;
});
/*global define*/
define('Core/PixelFormat',[
'./freezeObject',
'./WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* The format of a pixel, i.e., the number of components it has and what they represent.
*
* @exports PixelFormat
*/
var PixelFormat = {
/**
* A pixel format containing a depth value.
*
* @type {Number}
* @constant
*/
DEPTH_COMPONENT : WebGLConstants.DEPTH_COMPONENT,
/**
* A pixel format containing a depth and stencil value, most often used with {@link PixelDatatype.UNSIGNED_INT_24_8}.
*
* @type {Number}
* @constant
*/
DEPTH_STENCIL : WebGLConstants.DEPTH_STENCIL,
/**
* A pixel format containing an alpha channel.
*
* @type {Number}
* @constant
*/
ALPHA : WebGLConstants.ALPHA,
/**
* A pixel format containing red, green, and blue channels.
*
* @type {Number}
* @constant
*/
RGB : WebGLConstants.RGB,
/**
* A pixel format containing red, green, blue, and alpha channels.
*
* @type {Number}
* @constant
*/
RGBA : WebGLConstants.RGBA,
/**
* A pixel format containing a luminance (intensity) channel.
*
* @type {Number}
* @constant
*/
LUMINANCE : WebGLConstants.LUMINANCE,
/**
* A pixel format containing luminance (intensity) and alpha channels.
*
* @type {Number}
* @constant
*/
LUMINANCE_ALPHA : WebGLConstants.LUMINANCE_ALPHA,
/**
* @private
*/
validate : function(pixelFormat) {
return pixelFormat === PixelFormat.DEPTH_COMPONENT ||
pixelFormat === PixelFormat.DEPTH_STENCIL ||
pixelFormat === PixelFormat.ALPHA ||
pixelFormat === PixelFormat.RGB ||
pixelFormat === PixelFormat.RGBA ||
pixelFormat === PixelFormat.LUMINANCE ||
pixelFormat === PixelFormat.LUMINANCE_ALPHA;
},
/**
* @private
*/
isColorFormat : function(pixelFormat) {
return pixelFormat === PixelFormat.ALPHA ||
pixelFormat === PixelFormat.RGB ||
pixelFormat === PixelFormat.RGBA ||
pixelFormat === PixelFormat.LUMINANCE ||
pixelFormat === PixelFormat.LUMINANCE_ALPHA;
},
/**
* @private
*/
isDepthFormat : function(pixelFormat) {
return pixelFormat === PixelFormat.DEPTH_COMPONENT ||
pixelFormat === PixelFormat.DEPTH_STENCIL;
}
};
return freezeObject(PixelFormat);
});
/*global define*/
define('Core/PointGeometry',[
'./BoundingSphere',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./PrimitiveType'
], function(
BoundingSphere,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Geometry,
GeometryAttribute,
GeometryAttributes,
PrimitiveType) {
'use strict';
/**
* Describes a collection of points made up of positions and colors.
*
* @alias PointGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {TypedArray} options.positionsTypedArray The position values of the points stored in a typed array. Positions are stored as packed (x, y, z) floats.
* @param {TypedArray} options.colorsTypedArray The color values of the points stored in a typed array. Colors are stored as packed (r, g, b) unsigned bytes.
* @param {BoundingSphere} [options.boundingSphere] Optional precomputed bounding sphere to save computation time.
*
* @example
* // Create a PointGeometry with two points
* var points = new Cesium.PointGeometry({
* positionsTypedArray : new Float32Array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
* colorsTypedArray : new Uint8Array([255, 0, 0, 127, 127, 127]),
* boundingSphere : boundingSphere
* });
* var geometry = Cesium.PointGeometry.createGeometry(points);
*
* @private
*/
function PointGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.positionsTypedArray)) {
throw new DeveloperError('options.positionsTypedArray is required.');
}
if (!defined(options.colorsTypedArray)) {
throw new DeveloperError('options.colorsTypedArray is required');
}
this._positionsTypedArray = options.positionsTypedArray;
this._colorsTypedArray = options.colorsTypedArray;
this._boundingSphere = BoundingSphere.clone(options.boundingSphere);
this._workerName = 'createPointGeometry';
}
/**
* Computes the geometric representation a point collection, including its vertices and a bounding sphere.
*
* @param {PointGeometry} pointGeometry A description of the points.
* @returns {Geometry} The computed vertices.
*/
PointGeometry.createGeometry = function(pointGeometry) {
var positions = pointGeometry._positionsTypedArray;
var componentByteLength = positions.byteLength / positions.length;
var componentDatatype = componentByteLength === 4 ? ComponentDatatype.FLOAT : ComponentDatatype.DOUBLE;
var attributes = new GeometryAttributes();
attributes.position = new GeometryAttribute({
componentDatatype : componentDatatype,
componentsPerAttribute : 3,
values : positions
});
attributes.color = new GeometryAttribute({
componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
componentsPerAttribute : 3,
values : pointGeometry._colorsTypedArray,
normalize : true
});
// User provided bounding sphere to save computation time.
var boundingSphere = pointGeometry._boundingSphere;
if (!defined(boundingSphere)) {
boundingSphere = BoundingSphere.fromVertices(positions);
}
return new Geometry({
attributes : attributes,
primitiveType : PrimitiveType.POINTS,
boundingSphere : boundingSphere
});
};
return PointGeometry;
});
/*global define*/
define('Core/pointInsideTriangle',[
'./barycentricCoordinates',
'./Cartesian3'
], function(
barycentricCoordinates,
Cartesian3) {
'use strict';
var coords = new Cartesian3();
/**
* Determines if a point is inside a triangle.
*
* @exports pointInsideTriangle
*
* @param {Cartesian2|Cartesian3} point The point to test.
* @param {Cartesian2|Cartesian3} p0 The first point of the triangle.
* @param {Cartesian2|Cartesian3} p1 The second point of the triangle.
* @param {Cartesian2|Cartesian3} p2 The third point of the triangle.
* @returns {Boolean} true
if the point is inside the triangle; otherwise, false
.
*
* @example
* // Returns true
* var p = new Cesium.Cartesian2(0.25, 0.25);
* var b = Cesium.pointInsideTriangle(p,
* new Cesium.Cartesian2(0.0, 0.0),
* new Cesium.Cartesian2(1.0, 0.0),
* new Cesium.Cartesian2(0.0, 1.0));
*/
function pointInsideTriangle(point, p0, p1, p2) {
barycentricCoordinates(point, p0, p1, p2, coords);
return (coords.x > 0.0) && (coords.y > 0.0) && (coords.z > 0);
}
return pointInsideTriangle;
});
/*global define*/
define('Core/Queue',[
'./defineProperties'
], function(
defineProperties) {
'use strict';
/**
* A queue that can enqueue items at the end, and dequeue items from the front.
*
* @alias Queue
* @constructor
*/
function Queue() {
this._array = [];
this._offset = 0;
this._length = 0;
}
defineProperties(Queue.prototype, {
/**
* The length of the queue.
*
* @memberof Queue.prototype
*
* @type {Number}
* @readonly
*/
length : {
get : function() {
return this._length;
}
}
});
/**
* Enqueues the specified item.
*
* @param {Object} item The item to enqueue.
*/
Queue.prototype.enqueue = function(item) {
this._array.push(item);
this._length++;
};
/**
* Dequeues an item. Returns undefined if the queue is empty.
*
* @returns {Object} The the dequeued item.
*/
Queue.prototype.dequeue = function() {
if (this._length === 0) {
return undefined;
}
var array = this._array;
var offset = this._offset;
var item = array[offset];
array[offset] = undefined;
offset++;
if ((offset > 10) && (offset * 2 > array.length)) {
//compact array
this._array = array.slice(offset);
offset = 0;
}
this._offset = offset;
this._length--;
return item;
};
/**
* Returns the item at the front of the queue. Returns undefined if the queue is empty.
*
* @returns {Object} The item at the front of the queue.
*/
Queue.prototype.peek = function() {
if (this._length === 0) {
return undefined;
}
return this._array[this._offset];
};
/**
* Check whether this queue contains the specified item.
*
* @param {Object} item The item to search for.
*/
Queue.prototype.contains = function(item) {
return this._array.indexOf(item) !== -1;
};
/**
* Remove all items from the queue.
*/
Queue.prototype.clear = function() {
this._array.length = this._offset = this._length = 0;
};
/**
* Sort the items in the queue in-place.
*
* @param {Queue~Comparator} compareFunction A function that defines the sort order.
*/
Queue.prototype.sort = function(compareFunction) {
if (this._offset > 0) {
//compact array
this._array = this._array.slice(this._offset);
this._offset = 0;
}
this._array.sort(compareFunction);
};
/**
* A function used to compare two items while sorting a queue.
* @callback Queue~Comparator
*
* @param {Object} a An item in the array.
* @param {Object} b An item in the array.
* @returns {Number} Returns a negative value if a
is less than b
,
* a positive value if a
is greater than b
, or
* 0 if a
is equal to b
.
*
* @example
* function compareNumbers(a, b) {
* return a - b;
* }
*/
return Queue;
});
/*global define*/
define('Core/PolygonGeometryLibrary',[
'./arrayRemoveDuplicates',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./GeometryPipeline',
'./IndexDatatype',
'./Math',
'./PolygonPipeline',
'./PrimitiveType',
'./Queue',
'./WindingOrder'
], function(
arrayRemoveDuplicates,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryPipeline,
IndexDatatype,
CesiumMath,
PolygonPipeline,
PrimitiveType,
Queue,
WindingOrder) {
'use strict';
/**
* @private
*/
var PolygonGeometryLibrary = {};
PolygonGeometryLibrary.computeHierarchyPackedLength = function(polygonHierarchy) {
var numComponents = 0;
var stack = [polygonHierarchy];
while (stack.length > 0) {
var hierarchy = stack.pop();
if (!defined(hierarchy)) {
continue;
}
numComponents += 2;
var positions = hierarchy.positions;
var holes = hierarchy.holes;
if (defined(positions)) {
numComponents += positions.length * Cartesian3.packedLength;
}
if (defined(holes)) {
var length = holes.length;
for (var i = 0; i < length; ++i) {
stack.push(holes[i]);
}
}
}
return numComponents;
};
PolygonGeometryLibrary.packPolygonHierarchy = function(polygonHierarchy, array, startingIndex) {
var stack = [polygonHierarchy];
while (stack.length > 0) {
var hierarchy = stack.pop();
if (!defined(hierarchy)) {
continue;
}
var positions = hierarchy.positions;
var holes = hierarchy.holes;
array[startingIndex++] = defined(positions) ? positions.length : 0;
array[startingIndex++] = defined(holes) ? holes.length : 0;
if (defined(positions)) {
var positionsLength = positions.length;
for (var i = 0; i < positionsLength; ++i, startingIndex += 3) {
Cartesian3.pack(positions[i], array, startingIndex);
}
}
if (defined(holes)) {
var holesLength = holes.length;
for (var j = 0; j < holesLength; ++j) {
stack.push(holes[j]);
}
}
}
return startingIndex;
};
PolygonGeometryLibrary.unpackPolygonHierarchy = function(array, startingIndex) {
var positionsLength = array[startingIndex++];
var holesLength = array[startingIndex++];
var positions = new Array(positionsLength);
var holes = holesLength > 0 ? new Array(holesLength) : undefined;
for (var i = 0; i < positionsLength; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
for (var j = 0; j < holesLength; ++j) {
holes[j] = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex);
startingIndex = holes[j].startingIndex;
delete holes[j].startingIndex;
}
return {
positions : positions,
holes : holes,
startingIndex : startingIndex
};
};
var distanceScratch = new Cartesian3();
function getPointAtDistance(p0, p1, distance, length) {
Cartesian3.subtract(p1, p0, distanceScratch);
Cartesian3.multiplyByScalar(distanceScratch, distance / length, distanceScratch);
Cartesian3.add(p0, distanceScratch, distanceScratch);
return [distanceScratch.x, distanceScratch.y, distanceScratch.z];
}
PolygonGeometryLibrary.subdivideLineCount = function(p0, p1, minDistance) {
var distance = Cartesian3.distance(p0, p1);
var n = distance / minDistance;
var countDivide = Math.max(0, Math.ceil(Math.log(n) / Math.log(2)));
return Math.pow(2, countDivide);
};
PolygonGeometryLibrary.subdivideLine = function(p0, p1, minDistance, result) {
var numVertices = PolygonGeometryLibrary.subdivideLineCount(p0, p1, minDistance);
var length = Cartesian3.distance(p0, p1);
var distanceBetweenVertices = length / numVertices;
if (!defined(result)) {
result = [];
}
var positions = result;
positions.length = numVertices * 3;
var index = 0;
for ( var i = 0; i < numVertices; i++) {
var p = getPointAtDistance(p0, p1, i * distanceBetweenVertices, length);
positions[index++] = p[0];
positions[index++] = p[1];
positions[index++] = p[2];
}
return positions;
};
var scaleToGeodeticHeightN1 = new Cartesian3();
var scaleToGeodeticHeightN2 = new Cartesian3();
var scaleToGeodeticHeightP1 = new Cartesian3();
var scaleToGeodeticHeightP2 = new Cartesian3();
PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid, perPositionHeight) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var n1 = scaleToGeodeticHeightN1;
var n2 = scaleToGeodeticHeightN2;
var p = scaleToGeodeticHeightP1;
var p2 = scaleToGeodeticHeightP2;
if (defined(geometry) && defined(geometry.attributes) && defined(geometry.attributes.position)) {
var positions = geometry.attributes.position.values;
var length = positions.length / 2;
for ( var i = 0; i < length; i += 3) {
Cartesian3.fromArray(positions, i, p);
ellipsoid.geodeticSurfaceNormal(p, n1);
p2 = ellipsoid.scaleToGeodeticSurface(p, p2);
n2 = Cartesian3.multiplyByScalar(n1, minHeight, n2);
n2 = Cartesian3.add(p2, n2, n2);
positions[i + length] = n2.x;
positions[i + 1 + length] = n2.y;
positions[i + 2 + length] = n2.z;
if (perPositionHeight) {
p2 = Cartesian3.clone(p, p2);
}
n2 = Cartesian3.multiplyByScalar(n1, maxHeight, n2);
n2 = Cartesian3.add(p2, n2, n2);
positions[i] = n2.x;
positions[i + 1] = n2.y;
positions[i + 2] = n2.z;
}
}
return geometry;
};
PolygonGeometryLibrary.polygonsFromHierarchy = function(polygonHierarchy, perPositionHeight, tangentPlane, ellipsoid) {
// create from a polygon hierarchy
// Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
var hierarchy = [];
var polygons = [];
var queue = new Queue();
queue.enqueue(polygonHierarchy);
while (queue.length !== 0) {
var outerNode = queue.dequeue();
var outerRing = outerNode.positions;
var holes = outerNode.holes;
outerRing = arrayRemoveDuplicates(outerRing, Cartesian3.equalsEpsilon, true);
if (outerRing.length < 3) {
continue;
}
var positions2D = tangentPlane.projectPointsOntoPlane(outerRing);
var holeIndices = [];
var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
if (originalWindingOrder === WindingOrder.CLOCKWISE) {
positions2D.reverse();
outerRing = outerRing.slice().reverse();
}
var positions = outerRing.slice();
var numChildren = defined(holes) ? holes.length : 0;
var polygonHoles = [];
var i;
var j;
for (i = 0; i < numChildren; i++) {
var hole = holes[i];
var holePositions = arrayRemoveDuplicates(hole.positions, Cartesian3.equalsEpsilon, true);
if (holePositions.length < 3) {
continue;
}
var holePositions2D = tangentPlane.projectPointsOntoPlane(holePositions);
originalWindingOrder = PolygonPipeline.computeWindingOrder2D(holePositions2D);
if (originalWindingOrder === WindingOrder.CLOCKWISE) {
holePositions2D.reverse();
holePositions = holePositions.slice().reverse();
}
polygonHoles.push(holePositions);
holeIndices.push(positions.length);
positions = positions.concat(holePositions);
positions2D = positions2D.concat(holePositions2D);
var numGrandchildren = 0;
if (defined(hole.holes)) {
numGrandchildren = hole.holes.length;
}
for (j = 0; j < numGrandchildren; j++) {
queue.enqueue(hole.holes[j]);
}
}
if (!perPositionHeight) {
for (i = 0; i < outerRing.length; i++) {
ellipsoid.scaleToGeodeticSurface(outerRing[i], outerRing[i]);
}
for (i = 0; i < polygonHoles.length; i++) {
var polygonHole = polygonHoles[i];
for (j = 0; j < polygonHole.length; ++j) {
ellipsoid.scaleToGeodeticSurface(polygonHole[j], polygonHole[j]);
}
}
}
hierarchy.push({
outerRing : outerRing,
holes : polygonHoles
});
polygons.push({
positions : positions,
positions2D : positions2D,
holes : holeIndices
});
}
return {
hierarchy : hierarchy,
polygons : polygons
};
};
PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat) {
var indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes);
/* If polygon is completely unrenderable, just use the first three vertices */
if (indices.length < 3) {
indices = [0, 1, 2];
}
var positions = polygon.positions;
if (perPositionHeight) {
var length = positions.length;
var flattenedPositions = new Array(length * 3);
var index = 0;
for ( var i = 0; i < length; i++) {
var p = positions[i];
flattenedPositions[index++] = p.x;
flattenedPositions[index++] = p.y;
flattenedPositions[index++] = p.z;
}
var geometry = new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : flattenedPositions
})
},
indices : indices,
primitiveType : PrimitiveType.TRIANGLES
});
if (vertexFormat.normal) {
return GeometryPipeline.computeNormal(geometry);
}
return geometry;
}
return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity);
};
var computeWallIndicesSubdivided = [];
var p1Scratch = new Cartesian3();
var p2Scratch = new Cartesian3();
PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) {
var edgePositions;
var topEdgeLength;
var i;
var p1;
var p2;
var length = positions.length;
var index = 0;
if (!perPositionHeight) {
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
var numVertices = 0;
for (i = 0; i < length; i++) {
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
}
topEdgeLength = (numVertices + length) * 3;
edgePositions = new Array(topEdgeLength * 2);
for (i = 0; i < length; i++) {
p1 = positions[i];
p2 = positions[(i + 1) % length];
var tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided);
var tempPositionsLength = tempPositions.length;
for (var j = 0; j < tempPositionsLength; ++j, ++index) {
edgePositions[index] = tempPositions[j];
edgePositions[index + topEdgeLength] = tempPositions[j];
}
edgePositions[index] = p2.x;
edgePositions[index + topEdgeLength] = p2.x;
++index;
edgePositions[index] = p2.y;
edgePositions[index + topEdgeLength] = p2.y;
++index;
edgePositions[index] = p2.z;
edgePositions[index + topEdgeLength] = p2.z;
++index;
}
} else {
topEdgeLength = length * 3 * 2;
edgePositions = new Array(topEdgeLength * 2);
for (i = 0; i < length; i++) {
p1 = positions[i];
p2 = positions[(i + 1) % length];
edgePositions[index] = edgePositions[index + topEdgeLength] = p1.x;
++index;
edgePositions[index] = edgePositions[index + topEdgeLength] = p1.y;
++index;
edgePositions[index] = edgePositions[index + topEdgeLength] = p1.z;
++index;
edgePositions[index] = edgePositions[index + topEdgeLength] = p2.x;
++index;
edgePositions[index] = edgePositions[index + topEdgeLength] = p2.y;
++index;
edgePositions[index] = edgePositions[index + topEdgeLength] = p2.z;
++index;
}
}
length = edgePositions.length;
var indices = IndexDatatype.createTypedArray(length / 3, length - positions.length * 6);
var edgeIndex = 0;
length /= 6;
for (i = 0; i < length; i++) {
var UL = i;
var UR = UL + 1;
var LL = UL + length;
var LR = LL + 1;
p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch);
p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch);
if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON14)) {
continue;
}
indices[edgeIndex++] = UL;
indices[edgeIndex++] = LL;
indices[edgeIndex++] = UR;
indices[edgeIndex++] = UR;
indices[edgeIndex++] = LL;
indices[edgeIndex++] = LR;
}
return new Geometry({
attributes : new GeometryAttributes({
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : edgePositions
})
}),
indices : indices,
primitiveType : PrimitiveType.TRIANGLES
});
};
return PolygonGeometryLibrary;
});
/*global define*/
define('Core/PolygonGeometry',[
'./BoundingRectangle',
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./Cartographic',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./EllipsoidTangentPlane',
'./Geometry',
'./GeometryAttribute',
'./GeometryInstance',
'./GeometryPipeline',
'./IndexDatatype',
'./Math',
'./Matrix3',
'./PolygonGeometryLibrary',
'./PolygonPipeline',
'./Quaternion',
'./Rectangle',
'./VertexFormat',
'./WindingOrder'
], function(
BoundingRectangle,
BoundingSphere,
Cartesian2,
Cartesian3,
Cartographic,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
EllipsoidTangentPlane,
Geometry,
GeometryAttribute,
GeometryInstance,
GeometryPipeline,
IndexDatatype,
CesiumMath,
Matrix3,
PolygonGeometryLibrary,
PolygonPipeline,
Quaternion,
Rectangle,
VertexFormat,
WindingOrder) {
'use strict';
var computeBoundingRectangleCartesian2 = new Cartesian2();
var computeBoundingRectangleCartesian3 = new Cartesian3();
var computeBoundingRectangleQuaternion = new Quaternion();
var computeBoundingRectangleMatrix3 = new Matrix3();
function computeBoundingRectangle(tangentPlane, positions, angle, result) {
var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, angle, computeBoundingRectangleQuaternion);
var textureMatrix = Matrix3.fromQuaternion(rotation, computeBoundingRectangleMatrix3);
var minX = Number.POSITIVE_INFINITY;
var maxX = Number.NEGATIVE_INFINITY;
var minY = Number.POSITIVE_INFINITY;
var maxY = Number.NEGATIVE_INFINITY;
var length = positions.length;
for ( var i = 0; i < length; ++i) {
var p = Cartesian3.clone(positions[i], computeBoundingRectangleCartesian3);
Matrix3.multiplyByVector(textureMatrix, p, p);
var st = tangentPlane.projectPointOntoPlane(p, computeBoundingRectangleCartesian2);
if (defined(st)) {
minX = Math.min(minX, st.x);
maxX = Math.max(maxX, st.x);
minY = Math.min(minY, st.y);
maxY = Math.max(maxY, st.y);
}
}
result.x = minX;
result.y = minY;
result.width = maxX - minX;
result.height = maxY - minY;
return result;
}
var scratchCarto1 = new Cartographic();
var scratchCarto2 = new Cartographic();
function adjustPosHeightsForNormal(position, p1, p2, ellipsoid) {
var carto1 = ellipsoid.cartesianToCartographic(position, scratchCarto1);
var height = carto1.height;
var p1Carto = ellipsoid.cartesianToCartographic(p1, scratchCarto2);
p1Carto.height = height;
ellipsoid.cartographicToCartesian(p1Carto, p1);
var p2Carto = ellipsoid.cartesianToCartographic(p2, scratchCarto2);
p2Carto.height = height - 100;
ellipsoid.cartographicToCartesian(p2Carto, p2);
}
var scratchBoundingRectangle = new BoundingRectangle();
var scratchPosition = new Cartesian3();
var scratchNormal = new Cartesian3();
var scratchTangent = new Cartesian3();
var scratchBinormal = new Cartesian3();
var p1Scratch = new Cartesian3();
var p2Scratch = new Cartesian3();
var scratchPerPosNormal = new Cartesian3();
var scratchPerPosTangent = new Cartesian3();
var scratchPerPosBinormal = new Cartesian3();
var appendTextureCoordinatesOrigin = new Cartesian2();
var appendTextureCoordinatesCartesian2 = new Cartesian2();
var appendTextureCoordinatesCartesian3 = new Cartesian3();
var appendTextureCoordinatesQuaternion = new Quaternion();
var appendTextureCoordinatesMatrix3 = new Matrix3();
function computeAttributes(options) {
var vertexFormat = options.vertexFormat;
var geometry = options.geometry;
if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
// PERFORMANCE_IDEA: Compute before subdivision, then just interpolate during subdivision.
// PERFORMANCE_IDEA: Compute with createGeometryFromPositions() for fast path when there's no holes.
var boundingRectangle = options.boundingRectangle;
var tangentPlane = options.tangentPlane;
var ellipsoid = options.ellipsoid;
var stRotation = options.stRotation;
var wall = options.wall;
var top = options.top || wall;
var bottom = options.bottom || wall;
var perPositionHeight = options.perPositionHeight;
var origin = appendTextureCoordinatesOrigin;
origin.x = boundingRectangle.x;
origin.y = boundingRectangle.y;
var flatPositions = geometry.attributes.position.values;
var length = flatPositions.length;
var textureCoordinates = vertexFormat.st ? new Float32Array(2 * (length / 3)) : undefined;
var normals;
if (vertexFormat.normal) {
if (perPositionHeight && top && !wall) {
normals = geometry.attributes.normal.values;
} else {
normals = new Float32Array(length);
}
}
var tangents = vertexFormat.tangent ? new Float32Array(length) : undefined;
var binormals = vertexFormat.binormal ? new Float32Array(length) : undefined;
var textureCoordIndex = 0;
var attrIndex = 0;
var normal = scratchNormal;
var tangent = scratchTangent;
var binormal = scratchBinormal;
var recomputeNormal = true;
var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, stRotation, appendTextureCoordinatesQuaternion);
var textureMatrix = Matrix3.fromQuaternion(rotation, appendTextureCoordinatesMatrix3);
var bottomOffset = 0;
var bottomOffset2 = 0;
if (top && bottom) {
bottomOffset = length / 2;
bottomOffset2 = length / 3;
length /= 2;
}
for ( var i = 0; i < length; i += 3) {
var position = Cartesian3.fromArray(flatPositions, i, appendTextureCoordinatesCartesian3);
if (vertexFormat.st) {
var p = Matrix3.multiplyByVector(textureMatrix, position, scratchPosition);
p = ellipsoid.scaleToGeodeticSurface(p,p);
var st = tangentPlane.projectPointOntoPlane(p, appendTextureCoordinatesCartesian2);
Cartesian2.subtract(st, origin, st);
var stx = CesiumMath.clamp(st.x / boundingRectangle.width, 0, 1);
var sty = CesiumMath.clamp(st.y / boundingRectangle.height, 0, 1);
if (bottom) {
textureCoordinates[textureCoordIndex + bottomOffset2] = stx;
textureCoordinates[textureCoordIndex + 1 + bottomOffset2] = sty;
}
if (top) {
textureCoordinates[textureCoordIndex] = stx;
textureCoordinates[textureCoordIndex + 1] = sty;
}
textureCoordIndex += 2;
}
if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
var attrIndex1 = attrIndex + 1;
var attrIndex2 = attrIndex + 2;
if (wall) {
if (i + 3 < length) {
var p1 = Cartesian3.fromArray(flatPositions, i + 3, p1Scratch);
if (recomputeNormal) {
var p2 = Cartesian3.fromArray(flatPositions, i + length, p2Scratch);
if (perPositionHeight) {
adjustPosHeightsForNormal(position, p1, p2, ellipsoid);
}
Cartesian3.subtract(p1, position, p1);
Cartesian3.subtract(p2, position, p2);
normal = Cartesian3.normalize(Cartesian3.cross(p2, p1, normal), normal);
recomputeNormal = false;
}
if (Cartesian3.equalsEpsilon(p1, position, CesiumMath.EPSILON10)) { // if we've reached a corner
recomputeNormal = true;
}
}
if (vertexFormat.tangent || vertexFormat.binormal) {
binormal = ellipsoid.geodeticSurfaceNormal(position, binormal);
if (vertexFormat.tangent) {
tangent = Cartesian3.normalize(Cartesian3.cross(binormal, normal, tangent), tangent);
}
}
} else {
normal = ellipsoid.geodeticSurfaceNormal(position, normal);
if (vertexFormat.tangent || vertexFormat.binormal) {
if (perPositionHeight) {
scratchPerPosNormal = Cartesian3.fromArray(normals, attrIndex, scratchPerPosNormal);
scratchPerPosTangent = Cartesian3.cross(Cartesian3.UNIT_Z, scratchPerPosNormal, scratchPerPosTangent);
scratchPerPosTangent = Cartesian3.normalize(Matrix3.multiplyByVector(textureMatrix, scratchPerPosTangent, scratchPerPosTangent), scratchPerPosTangent);
if (vertexFormat.binormal) {
scratchPerPosBinormal = Cartesian3.normalize(Cartesian3.cross(scratchPerPosNormal, scratchPerPosTangent, scratchPerPosBinormal), scratchPerPosBinormal);
}
}
tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent);
tangent = Cartesian3.normalize(Matrix3.multiplyByVector(textureMatrix, tangent, tangent), tangent);
if (vertexFormat.binormal) {
binormal = Cartesian3.normalize(Cartesian3.cross(normal, tangent, binormal), binormal);
}
}
}
if (vertexFormat.normal) {
if (options.wall) {
normals[attrIndex + bottomOffset] = normal.x;
normals[attrIndex1 + bottomOffset] = normal.y;
normals[attrIndex2 + bottomOffset] = normal.z;
} else if (bottom){
normals[attrIndex + bottomOffset] = -normal.x;
normals[attrIndex1 + bottomOffset] = -normal.y;
normals[attrIndex2 + bottomOffset] = -normal.z;
}
if ((top && !perPositionHeight) || wall) {
normals[attrIndex] = normal.x;
normals[attrIndex1] = normal.y;
normals[attrIndex2] = normal.z;
}
}
if (vertexFormat.tangent) {
if (options.wall) {
tangents[attrIndex + bottomOffset] = tangent.x;
tangents[attrIndex1 + bottomOffset] = tangent.y;
tangents[attrIndex2 + bottomOffset] = tangent.z;
} else if (bottom) {
tangents[attrIndex + bottomOffset] = -tangent.x;
tangents[attrIndex1 + bottomOffset] = -tangent.y;
tangents[attrIndex2 + bottomOffset] = -tangent.z;
}
if(top) {
if (perPositionHeight) {
tangents[attrIndex] = scratchPerPosTangent.x;
tangents[attrIndex1] = scratchPerPosTangent.y;
tangents[attrIndex2] = scratchPerPosTangent.z;
} else {
tangents[attrIndex] = tangent.x;
tangents[attrIndex1] = tangent.y;
tangents[attrIndex2] = tangent.z;
}
}
}
if (vertexFormat.binormal) {
if (bottom) {
binormals[attrIndex + bottomOffset] = binormal.x;
binormals[attrIndex1 + bottomOffset] = binormal.y;
binormals[attrIndex2 + bottomOffset] = binormal.z;
}
if (top) {
if (perPositionHeight) {
binormals[attrIndex] = scratchPerPosBinormal.x;
binormals[attrIndex1] = scratchPerPosBinormal.y;
binormals[attrIndex2] = scratchPerPosBinormal.z;
} else {
binormals[attrIndex] = binormal.x;
binormals[attrIndex1] = binormal.y;
binormals[attrIndex2] = binormal.z;
}
}
}
attrIndex += 3;
}
}
if (vertexFormat.st) {
geometry.attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : textureCoordinates
});
}
if (vertexFormat.normal) {
geometry.attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.tangent) {
geometry.attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
geometry.attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
}
return geometry;
}
var createGeometryFromPositionsExtrudedPositions = [];
function createGeometryFromPositionsExtruded(ellipsoid, polygon, granularity, hierarchy, perPositionHeight, closeTop, closeBottom, vertexFormat) {
var geos = {
walls : []
};
var i;
if (closeTop || closeBottom) {
var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat);
var edgePoints = topGeo.attributes.position.values;
var indices = topGeo.indices;
var numPositions;
var newIndices;
if (closeTop && closeBottom) {
var topBottomPositions = edgePoints.concat(edgePoints);
numPositions = topBottomPositions.length / 3;
newIndices = IndexDatatype.createTypedArray(numPositions, indices.length * 2);
newIndices.set(indices);
var ilength = indices.length;
var length = numPositions / 2;
for (i = 0; i < ilength; i += 3) {
var i0 = newIndices[i] + length;
var i1 = newIndices[i + 1] + length;
var i2 = newIndices[i + 2] + length;
newIndices[i + ilength] = i2;
newIndices[i + 1 + ilength] = i1;
newIndices[i + 2 + ilength] = i0;
}
topGeo.attributes.position.values = topBottomPositions;
if (perPositionHeight) {
var normals = topGeo.attributes.normal.values;
topGeo.attributes.normal.values = new Float32Array(topBottomPositions.length);
topGeo.attributes.normal.values.set(normals);
}
topGeo.indices = newIndices;
} else if (closeBottom) {
numPositions = edgePoints.length / 3;
newIndices = IndexDatatype.createTypedArray(numPositions, indices.length);
for (i = 0; i < indices.length; i += 3) {
newIndices[i] = indices[i + 2];
newIndices[i + 1] = indices[i + 1];
newIndices[i + 2] = indices[i];
}
topGeo.indices = newIndices;
}
geos.topAndBottom = new GeometryInstance({
geometry : topGeo
});
}
var outerRing = hierarchy.outerRing;
var tangentPlane = EllipsoidTangentPlane.fromPoints(outerRing, ellipsoid);
var positions2D = tangentPlane.projectPointsOntoPlane(outerRing, createGeometryFromPositionsExtrudedPositions);
var windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
if (windingOrder === WindingOrder.CLOCKWISE) {
outerRing = outerRing.slice().reverse();
}
var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight);
geos.walls.push(new GeometryInstance({
geometry : wallGeo
}));
var holes = hierarchy.holes;
for (i = 0; i < holes.length; i++) {
var hole = holes[i];
tangentPlane = EllipsoidTangentPlane.fromPoints(hole, ellipsoid);
positions2D = tangentPlane.projectPointsOntoPlane(hole, createGeometryFromPositionsExtrudedPositions);
windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
if (windingOrder === WindingOrder.COUNTER_CLOCKWISE) {
hole = hole.slice().reverse();
}
wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity);
geos.walls.push(new GeometryInstance({
geometry : wallGeo
}));
}
return geos;
}
/**
* A description of a polygon on the ellipsoid. The polygon is defined by a polygon hierarchy. Polygon geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
*
* @alias PolygonGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {PolygonHierarchy} options.polygonHierarchy A polygon hierarchy that can include holes.
* @param {Number} [options.height=0.0] The distance in meters between the polygon and the ellipsoid surface.
* @param {Number} [options.extrudedHeight] The distance in meters between the polygon's extruded face and the ellipsoid surface.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
* @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
* @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
*
* @see PolygonGeometry#createGeometry
* @see PolygonGeometry#fromPositions
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon Demo}
*
* @example
* // 1. create a polygon from points
* var polygon = new Cesium.PolygonGeometry({
* polygonHierarchy : new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ])
* )
* });
* var geometry = Cesium.PolygonGeometry.createGeometry(polygon);
*
* // 2. create a nested polygon with holes
* var polygonWithHole = new Cesium.PolygonGeometry({
* polygonHierarchy : new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -109.0, 30.0,
* -95.0, 30.0,
* -95.0, 40.0,
* -109.0, 40.0
* ]),
* [new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -107.0, 31.0,
* -107.0, 39.0,
* -97.0, 39.0,
* -97.0, 31.0
* ]),
* [new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -105.0, 33.0,
* -99.0, 33.0,
* -99.0, 37.0,
* -105.0, 37.0
* ]),
* [new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -103.0, 34.0,
* -101.0, 34.0,
* -101.0, 36.0,
* -103.0, 36.0
* ])
* )]
* )]
* )]
* )
* });
* var geometry = Cesium.PolygonGeometry.createGeometry(polygonWithHole);
*
* // 3. create extruded polygon
* var extrudedPolygon = new Cesium.PolygonGeometry({
* polygonHierarchy : new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ])
* ),
* extrudedHeight: 300000
* });
* var geometry = Cesium.PolygonGeometry.createGeometry(extrudedPolygon);
*/
function PolygonGeometry(options) {
if (!defined(options) || !defined(options.polygonHierarchy)) {
throw new DeveloperError('options.polygonHierarchy is required.');
}
if (defined(options.perPositionHeight) && options.perPositionHeight && defined(options.height)) {
throw new DeveloperError('Cannot use both options.perPositionHeight and options.height');
}
var polygonHierarchy = options.polygonHierarchy;
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var stRotation = defaultValue(options.stRotation, 0.0);
var height = defaultValue(options.height, 0.0);
var perPositionHeight = defaultValue(options.perPositionHeight, false);
var extrudedHeight = options.extrudedHeight;
var extrude = defined(extrudedHeight);
if (!perPositionHeight && extrude) {
//Ignore extrudedHeight if it matches height
if (CesiumMath.equalsEpsilon(height, extrudedHeight, CesiumMath.EPSILON10)) {
extrudedHeight = undefined;
extrude = false;
} else {
var h = extrudedHeight;
extrudedHeight = Math.min(h, height);
height = Math.max(h, height);
}
}
this._vertexFormat = VertexFormat.clone(vertexFormat);
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._granularity = granularity;
this._stRotation = stRotation;
this._height = height;
this._extrudedHeight = defaultValue(extrudedHeight, 0.0);
this._extrude = extrude;
this._closeTop = defaultValue(options.closeTop, true);
this._closeBottom = defaultValue(options.closeBottom, true);
this._polygonHierarchy = polygonHierarchy;
this._perPositionHeight = perPositionHeight;
this._workerName = 'createPolygonGeometry';
var positions = polygonHierarchy.positions;
if (!defined(positions) || positions.length < 3) {
this._rectangle = new Rectangle();
} else {
this._rectangle = Rectangle.fromCartesianArray(positions, ellipsoid);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 9;
}
/**
* A description of a polygon from an array of positions. Polygon geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of positions that defined the corner points of the polygon.
* @param {Number} [options.height=0.0] The height of the polygon.
* @param {Number} [options.extrudedHeight] The height of the polygon extrusion.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {Number} [options.stRotation=0.0] The rotation of the texture coordiantes, in radians. A positive rotation is counter-clockwise.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
* @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
* @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
* @returns {PolygonGeometry}
*
*
* @example
* // create a polygon from points
* var polygon = Cesium.PolygonGeometry.fromPositions({
* positions : Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ])
* });
* var geometry = Cesium.PolygonGeometry.createGeometry(polygon);
*
* @see PolygonGeometry#createGeometry
*/
PolygonGeometry.fromPositions = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.positions)) {
throw new DeveloperError('options.positions is required.');
}
var newOptions = {
polygonHierarchy : {
positions : options.positions
},
height : options.height,
extrudedHeight : options.extrudedHeight,
vertexFormat : options.vertexFormat,
stRotation : options.stRotation,
ellipsoid : options.ellipsoid,
granularity : options.granularity,
perPositionHeight : options.perPositionHeight,
closeTop : options.closeTop,
closeBottom: options.closeBottom
};
return new PolygonGeometry(newOptions);
};
/**
* Stores the provided instance into the provided array.
*
* @param {PolygonGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
PolygonGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
startingIndex = PolygonGeometryLibrary.packPolygonHierarchy(value._polygonHierarchy, array, startingIndex);
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
Rectangle.pack(value._rectangle, array, startingIndex);
startingIndex += Rectangle.packedLength;
array[startingIndex++] = value._height;
array[startingIndex++] = value._extrudedHeight;
array[startingIndex++] = value._granularity;
array[startingIndex++] = value._stRotation;
array[startingIndex++] = value._extrude ? 1.0 : 0.0;
array[startingIndex++] = value._perPositionHeight ? 1.0 : 0.0;
array[startingIndex++] = value._closeTop ? 1.0 : 0.0;
array[startingIndex++] = value._closeBottom ? 1.0 : 0.0;
array[startingIndex] = value.packedLength;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchVertexFormat = new VertexFormat();
var scratchRectangle = new Rectangle();
//Only used to avoid inaability to default construct.
var dummyOptions = {
polygonHierarchy : {}
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {PolygonGeometry} [result] The object into which to store the result.
*/
PolygonGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex);
startingIndex = polygonHierarchy.startingIndex;
delete polygonHierarchy.startingIndex;
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
startingIndex += Rectangle.packedLength;
var height = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var granularity = array[startingIndex++];
var stRotation = array[startingIndex++];
var extrude = array[startingIndex++] === 1.0;
var perPositionHeight = array[startingIndex++] === 1.0;
var closeTop = array[startingIndex++] === 1.0;
var closeBottom = array[startingIndex++] === 1.0;
var packedLength = array[startingIndex];
if (!defined(result)) {
result = new PolygonGeometry(dummyOptions);
}
result._polygonHierarchy = polygonHierarchy;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._height = height;
result._extrudedHeight = extrudedHeight;
result._granularity = granularity;
result._stRotation = stRotation;
result._extrude = extrude;
result._perPositionHeight = perPositionHeight;
result._closeTop = closeTop;
result._closeBottom = closeBottom;
result._rectangle = Rectangle.clone(rectangle);
result.packedLength = packedLength;
return result;
};
/**
* Computes the geometric representation of a polygon, including its vertices, indices, and a bounding sphere.
*
* @param {PolygonGeometry} polygonGeometry A description of the polygon.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
PolygonGeometry.createGeometry = function(polygonGeometry) {
var vertexFormat = polygonGeometry._vertexFormat;
var ellipsoid = polygonGeometry._ellipsoid;
var granularity = polygonGeometry._granularity;
var stRotation = polygonGeometry._stRotation;
var height = polygonGeometry._height;
var extrudedHeight = polygonGeometry._extrudedHeight;
var extrude = polygonGeometry._extrude;
var polygonHierarchy = polygonGeometry._polygonHierarchy;
var perPositionHeight = polygonGeometry._perPositionHeight;
var closeTop = polygonGeometry._closeTop;
var closeBottom = polygonGeometry._closeBottom;
var outerPositions = polygonHierarchy.positions;
if (outerPositions.length < 3) {
return;
}
var tangentPlane = EllipsoidTangentPlane.fromPoints(outerPositions, ellipsoid);
var results = PolygonGeometryLibrary.polygonsFromHierarchy(polygonHierarchy, perPositionHeight, tangentPlane, ellipsoid);
var hierarchy = results.hierarchy;
var polygons = results.polygons;
if (hierarchy.length === 0) {
return;
}
outerPositions = hierarchy[0].outerRing;
var boundingRectangle = computeBoundingRectangle(tangentPlane, outerPositions, stRotation, scratchBoundingRectangle);
var geometry;
var geometries = [];
var options = {
perPositionHeight: perPositionHeight,
vertexFormat: vertexFormat,
geometry: undefined,
tangentPlane: tangentPlane,
boundingRectangle: boundingRectangle,
ellipsoid: ellipsoid,
stRotation: stRotation,
bottom: false,
top: true,
wall: false
};
var i;
if (extrude) {
options.top = closeTop;
options.bottom = closeBottom;
for (i = 0; i < polygons.length; i++) {
geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat);
var topAndBottom;
if (closeTop && closeBottom) {
topAndBottom = geometry.topAndBottom;
options.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(topAndBottom.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
} else if (closeTop) {
topAndBottom = geometry.topAndBottom;
topAndBottom.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(topAndBottom.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
options.geometry = topAndBottom.geometry;
} else if (closeBottom) {
topAndBottom = geometry.topAndBottom;
topAndBottom.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(topAndBottom.geometry.attributes.position.values, extrudedHeight, ellipsoid, true);
options.geometry = topAndBottom.geometry;
}
if (closeTop || closeBottom) {
options.wall = false;
topAndBottom.geometry = computeAttributes(options);
geometries.push(topAndBottom);
}
var walls = geometry.walls;
options.wall = true;
for ( var k = 0; k < walls.length; k++) {
var wall = walls[k];
options.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(wall.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
wall.geometry = computeAttributes(options);
geometries.push(wall);
}
}
} else {
for (i = 0; i < polygons.length; i++) {
geometry = new GeometryInstance({
geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight, vertexFormat)
});
geometry.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
options.geometry = geometry.geometry;
geometry.geometry = computeAttributes(options);
geometries.push(geometry);
}
}
geometry = GeometryPipeline.combineInstances(geometries)[0];
geometry.attributes.position.values = new Float64Array(geometry.attributes.position.values);
geometry.indices = IndexDatatype.createTypedArray(geometry.attributes.position.values.length / 3, geometry.indices);
var attributes = geometry.attributes;
var boundingSphere = BoundingSphere.fromVertices(attributes.position.values);
if (!vertexFormat.position) {
delete attributes.position;
}
return new Geometry({
attributes : attributes,
indices : geometry.indices,
primitiveType : geometry.primitiveType,
boundingSphere : boundingSphere
});
};
/**
* @private
*/
PolygonGeometry.createShadowVolume = function(polygonGeometry, minHeightFunc, maxHeightFunc) {
var granularity = polygonGeometry._granularity;
var ellipsoid = polygonGeometry._ellipsoid;
var minHeight = minHeightFunc(granularity, ellipsoid);
var maxHeight = maxHeightFunc(granularity, ellipsoid);
return new PolygonGeometry({
polygonHierarchy : polygonGeometry._polygonHierarchy,
ellipsoid : ellipsoid,
stRotation : polygonGeometry._stRotation,
granularity : granularity,
perPositionHeight : false,
extrudedHeight : minHeight,
height : maxHeight,
vertexFormat : VertexFormat.POSITION_ONLY
});
};
defineProperties(PolygonGeometry.prototype, {
/**
* @private
*/
rectangle : {
get : function() {
return this._rectangle;
}
}
});
return PolygonGeometry;
});
/*global define*/
define('Core/PolygonHierarchy',[
'./defined'
], function(
defined) {
'use strict';
/**
* An hierarchy of linear rings which define a polygon and its holes.
* The holes themselves may also have holes which nest inner polygons.
* @alias PolygonHierarchy
* @constructor
*
* @param {Cartesian3[]} [positions] A linear ring defining the outer boundary of the polygon or hole.
* @param {PolygonHierarchy[]} [holes] An array of polygon hierarchies defining holes in the polygon.
*/
function PolygonHierarchy(positions, holes) {
/**
* A linear ring defining the outer boundary of the polygon or hole.
* @type {Cartesian3[]}
*/
this.positions = defined(positions) ? positions : [];
/**
* An array of polygon hierarchies defining holes in the polygon.
* @type {PolygonHierarchy[]}
*/
this.holes = defined(holes) ? holes : [];
}
return PolygonHierarchy;
});
/*global define*/
define('Core/PolygonOutlineGeometry',[
'./arrayRemoveDuplicates',
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./EllipsoidTangentPlane',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./GeometryInstance',
'./GeometryPipeline',
'./IndexDatatype',
'./Math',
'./PolygonGeometryLibrary',
'./PolygonPipeline',
'./PrimitiveType',
'./Queue',
'./WindingOrder'
], function(
arrayRemoveDuplicates,
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
EllipsoidTangentPlane,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryInstance,
GeometryPipeline,
IndexDatatype,
CesiumMath,
PolygonGeometryLibrary,
PolygonPipeline,
PrimitiveType,
Queue,
WindingOrder) {
'use strict';
var createGeometryFromPositionsPositions = [];
var createGeometryFromPositionsSubdivided = [];
function createGeometryFromPositions(ellipsoid, positions, minDistance, perPositionHeight) {
var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid);
var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions);
var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
if (originalWindingOrder === WindingOrder.CLOCKWISE) {
positions2D.reverse();
positions = positions.slice().reverse();
}
var subdividedPositions;
var i;
var length = positions.length;
var index = 0;
if (!perPositionHeight) {
var numVertices = 0;
for (i = 0; i < length; i++) {
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
}
subdividedPositions = new Float64Array(numVertices * 3);
for (i = 0; i < length; i++) {
var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
var tempPositionsLength = tempPositions.length;
for (var j = 0; j < tempPositionsLength; ++j) {
subdividedPositions[index++] = tempPositions[j];
}
}
} else {
subdividedPositions = new Float64Array(length * 2 * 3);
for (i = 0; i < length; i++) {
var p0 = positions[i];
var p1 = positions[(i + 1) % length];
subdividedPositions[index++] = p0.x;
subdividedPositions[index++] = p0.y;
subdividedPositions[index++] = p0.z;
subdividedPositions[index++] = p1.x;
subdividedPositions[index++] = p1.y;
subdividedPositions[index++] = p1.z;
}
}
length = subdividedPositions.length / 3;
var indicesSize = length * 2;
var indices = IndexDatatype.createTypedArray(length, indicesSize);
index = 0;
for (i = 0; i < length - 1; i++) {
indices[index++] = i;
indices[index++] = i + 1;
}
indices[index++] = length - 1;
indices[index++] = 0;
return new GeometryInstance({
geometry : new Geometry({
attributes : new GeometryAttributes({
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : subdividedPositions
})
}),
indices : indices,
primitiveType : PrimitiveType.LINES
})
});
}
function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight) {
var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid);
var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions);
var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
if (originalWindingOrder === WindingOrder.CLOCKWISE) {
positions2D.reverse();
positions = positions.slice().reverse();
}
var subdividedPositions;
var i;
var length = positions.length;
var corners = new Array(length);
var index = 0;
if (!perPositionHeight) {
var numVertices = 0;
for (i = 0; i < length; i++) {
numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
}
subdividedPositions = new Float64Array(numVertices * 3 * 2);
for (i = 0; i < length; ++i) {
corners[i] = index / 3;
var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
var tempPositionsLength = tempPositions.length;
for (var j = 0; j < tempPositionsLength; ++j) {
subdividedPositions[index++] = tempPositions[j];
}
}
} else {
subdividedPositions = new Float64Array(length * 2 * 3 * 2);
for (i = 0; i < length; ++i) {
corners[i] = index / 3;
var p0 = positions[i];
var p1 = positions[(i + 1) % length];
subdividedPositions[index++] = p0.x;
subdividedPositions[index++] = p0.y;
subdividedPositions[index++] = p0.z;
subdividedPositions[index++] = p1.x;
subdividedPositions[index++] = p1.y;
subdividedPositions[index++] = p1.z;
}
}
length = subdividedPositions.length / (3 * 2);
var cornersLength = corners.length;
var indicesSize = ((length * 2) + cornersLength) * 2;
var indices = IndexDatatype.createTypedArray(length, indicesSize);
index = 0;
for (i = 0; i < length; ++i) {
indices[index++] = i;
indices[index++] = (i + 1) % length;
indices[index++] = i + length;
indices[index++] = ((i + 1) % length) + length;
}
for (i = 0; i < cornersLength; i++) {
var corner = corners[i];
indices[index++] = corner;
indices[index++] = corner + length;
}
return new GeometryInstance({
geometry : new Geometry({
attributes : new GeometryAttributes({
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : subdividedPositions
})
}),
indices : indices,
primitiveType : PrimitiveType.LINES
})
});
}
/**
* A description of the outline of a polygon on the ellipsoid. The polygon is defined by a polygon hierarchy.
*
* @alias PolygonOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {PolygonHierarchy} options.polygonHierarchy A polygon hierarchy that can include holes.
* @param {Number} [options.height=0.0] The distance in meters between the polygon and the ellipsoid surface.
* @param {Number} [options.extrudedHeight] The distance in meters between the polygon's extruded face and the ellipsoid surface.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
*
* @see PolygonOutlineGeometry#createGeometry
* @see PolygonOutlineGeometry#fromPositions
*
* @example
* // 1. create a polygon outline from points
* var polygon = new Cesium.PolygonOutlineGeometry({
* polygonHierarchy : new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ])
* )
* });
* var geometry = Cesium.PolygonOutlineGeometry.createGeometry(polygon);
*
* // 2. create a nested polygon with holes outline
* var polygonWithHole = new Cesium.PolygonOutlineGeometry({
* polygonHierarchy : new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -109.0, 30.0,
* -95.0, 30.0,
* -95.0, 40.0,
* -109.0, 40.0
* ]),
* [new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -107.0, 31.0,
* -107.0, 39.0,
* -97.0, 39.0,
* -97.0, 31.0
* ]),
* [new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -105.0, 33.0,
* -99.0, 33.0,
* -99.0, 37.0,
* -105.0, 37.0
* ]),
* [new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -103.0, 34.0,
* -101.0, 34.0,
* -101.0, 36.0,
* -103.0, 36.0
* ])
* )]
* )]
* )]
* )
* });
* var geometry = Cesium.PolygonOutlineGeometry.createGeometry(polygonWithHole);
*
* // 3. create extruded polygon outline
* var extrudedPolygon = new Cesium.PolygonOutlineGeometry({
* polygonHierarchy : new Cesium.PolygonHierarchy(
* Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ])
* ),
* extrudedHeight: 300000
* });
* var geometry = Cesium.PolygonOutlineGeometry.createGeometry(extrudedPolygon);
*/
function PolygonOutlineGeometry(options) {
if (!defined(options) || !defined(options.polygonHierarchy)) {
throw new DeveloperError('options.polygonHierarchy is required.');
}
if (defined(options.perPositionHeight) && options.perPositionHeight && defined(options.height)) {
throw new DeveloperError('Cannot use both options.perPositionHeight and options.height');
}
var polygonHierarchy = options.polygonHierarchy;
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var height = defaultValue(options.height, 0.0);
var perPositionHeight = defaultValue(options.perPositionHeight, false);
var extrudedHeight = options.extrudedHeight;
var extrude = defined(extrudedHeight);
if (extrude && !perPositionHeight) {
var h = extrudedHeight;
extrudedHeight = Math.min(h, height);
height = Math.max(h, height);
}
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._granularity = granularity;
this._height = height;
this._extrudedHeight = defaultValue(extrudedHeight, 0.0);
this._extrude = extrude;
this._polygonHierarchy = polygonHierarchy;
this._perPositionHeight = perPositionHeight;
this._workerName = 'createPolygonOutlineGeometry';
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 6;
}
/**
* Stores the provided instance into the provided array.
*
* @param {PolygonOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
PolygonOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
startingIndex = PolygonGeometryLibrary.packPolygonHierarchy(value._polygonHierarchy, array, startingIndex);
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex++] = value._height;
array[startingIndex++] = value._extrudedHeight;
array[startingIndex++] = value._granularity;
array[startingIndex++] = value._extrude ? 1.0 : 0.0;
array[startingIndex++] = value._perPositionHeight ? 1.0 : 0.0;
array[startingIndex++] = value.packedLength;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var dummyOptions = {
polygonHierarchy : {}
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {PolygonOutlineGeometry} [result] The object into which to store the result.
* @returns {PolygonOutlineGeometry} The modified result parameter or a new PolygonOutlineGeometry instance if one was not provided.
*/
PolygonOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex);
startingIndex = polygonHierarchy.startingIndex;
delete polygonHierarchy.startingIndex;
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var height = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var granularity = array[startingIndex++];
var extrude = array[startingIndex++] === 1.0;
var perPositionHeight = array[startingIndex++] === 1.0;
var packedLength = array[startingIndex++];
if (!defined(result)) {
result = new PolygonOutlineGeometry(dummyOptions);
}
result._polygonHierarchy = polygonHierarchy;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._height = height;
result._extrudedHeight = extrudedHeight;
result._granularity = granularity;
result._extrude = extrude;
result._perPositionHeight = perPositionHeight;
result.packedLength = packedLength;
return result;
};
/**
* A description of a polygon outline from an array of positions.
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of positions that defined the corner points of the polygon.
* @param {Number} [options.height=0.0] The height of the polygon.
* @param {Number} [options.extrudedHeight] The height of the polygon extrusion.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
* @returns {PolygonOutlineGeometry}
*
*
* @example
* // create a polygon from points
* var polygon = Cesium.PolygonOutlineGeometry.fromPositions({
* positions : Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0,
* -75.0, 30.0,
* -70.0, 30.0,
* -68.0, 40.0
* ])
* });
* var geometry = Cesium.PolygonOutlineGeometry.createGeometry(polygon);
*
* @see PolygonOutlineGeometry#createGeometry
*/
PolygonOutlineGeometry.fromPositions = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.positions)) {
throw new DeveloperError('options.positions is required.');
}
var newOptions = {
polygonHierarchy : {
positions : options.positions
},
height : options.height,
extrudedHeight : options.extrudedHeight,
ellipsoid : options.ellipsoid,
granularity : options.granularity,
perPositionHeight : options.perPositionHeight
};
return new PolygonOutlineGeometry(newOptions);
};
/**
* Computes the geometric representation of a polygon outline, including its vertices, indices, and a bounding sphere.
*
* @param {PolygonOutlineGeometry} polygonGeometry A description of the polygon outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
PolygonOutlineGeometry.createGeometry = function(polygonGeometry) {
var ellipsoid = polygonGeometry._ellipsoid;
var granularity = polygonGeometry._granularity;
var height = polygonGeometry._height;
var extrudedHeight = polygonGeometry._extrudedHeight;
var extrude = polygonGeometry._extrude;
var polygonHierarchy = polygonGeometry._polygonHierarchy;
var perPositionHeight = polygonGeometry._perPositionHeight;
// create from a polygon hierarchy
// Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
var polygons = [];
var queue = new Queue();
queue.enqueue(polygonHierarchy);
var i;
while (queue.length !== 0) {
var outerNode = queue.dequeue();
var outerRing = outerNode.positions;
outerRing = arrayRemoveDuplicates(outerRing, Cartesian3.equalsEpsilon, true);
if (outerRing.length < 3) {
continue;
}
var numChildren = outerNode.holes ? outerNode.holes.length : 0;
// The outer polygon contains inner polygons
for (i = 0; i < numChildren; i++) {
var hole = outerNode.holes[i];
hole.positions = arrayRemoveDuplicates(hole.positions, Cartesian3.equalsEpsilon, true);
if (hole.positions.length < 3) {
continue;
}
polygons.push(hole.positions);
var numGrandchildren = 0;
if (defined(hole.holes)) {
numGrandchildren = hole.holes.length;
}
for ( var j = 0; j < numGrandchildren; j++) {
queue.enqueue(hole.holes[j]);
}
}
polygons.push(outerRing);
}
if (polygons.length === 0) {
return undefined;
}
var geometry;
var geometries = [];
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
if (extrude) {
for (i = 0; i < polygons.length; i++) {
geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], minDistance, perPositionHeight);
geometry.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(geometry.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
geometries.push(geometry);
}
} else {
for (i = 0; i < polygons.length; i++) {
geometry = createGeometryFromPositions(ellipsoid, polygons[i], minDistance, perPositionHeight);
geometry.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
geometries.push(geometry);
}
}
geometry = GeometryPipeline.combineInstances(geometries)[0];
var boundingSphere = BoundingSphere.fromVertices(geometry.attributes.position.values);
return new Geometry({
attributes : geometry.attributes,
indices : geometry.indices,
primitiveType : geometry.primitiveType,
boundingSphere : boundingSphere
});
};
return PolygonOutlineGeometry;
});
/*global define*/
define('Core/PolylineGeometry',[
'./arrayRemoveDuplicates',
'./BoundingSphere',
'./Cartesian3',
'./Color',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./GeometryType',
'./IndexDatatype',
'./Math',
'./PolylinePipeline',
'./PrimitiveType',
'./VertexFormat'
], function(
arrayRemoveDuplicates,
BoundingSphere,
Cartesian3,
Color,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryType,
IndexDatatype,
CesiumMath,
PolylinePipeline,
PrimitiveType,
VertexFormat) {
'use strict';
var scratchInterpolateColorsArray = [];
function interpolateColors(p0, p1, color0, color1, numPoints) {
var colors = scratchInterpolateColorsArray;
colors.length = numPoints;
var i;
var r0 = color0.red;
var g0 = color0.green;
var b0 = color0.blue;
var a0 = color0.alpha;
var r1 = color1.red;
var g1 = color1.green;
var b1 = color1.blue;
var a1 = color1.alpha;
if (Color.equals(color0, color1)) {
for (i = 0; i < numPoints; i++) {
colors[i] = Color.clone(color0);
}
return colors;
}
var redPerVertex = (r1 - r0) / numPoints;
var greenPerVertex = (g1 - g0) / numPoints;
var bluePerVertex = (b1 - b0) / numPoints;
var alphaPerVertex = (a1 - a0) / numPoints;
for (i = 0; i < numPoints; i++) {
colors[i] = new Color(r0 + i * redPerVertex, g0 + i * greenPerVertex, b0 + i * bluePerVertex, a0 + i * alphaPerVertex);
}
return colors;
}
/**
* A description of a polyline modeled as a line strip; the first two positions define a line segment,
* and each additional position defines a line segment from the previous position. The polyline is capable of
* displaying with a material.
*
* @alias PolylineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the positions in the polyline as a line strip.
* @param {Number} [options.width=1.0] The width in pixels.
* @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors.
* @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices.
* @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
*
* @exception {DeveloperError} At least two positions are required.
* @exception {DeveloperError} width must be greater than or equal to one.
* @exception {DeveloperError} colors has an invalid length.
*
* @see PolylineGeometry#createGeometry
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline.html|Cesium Sandcastle Polyline Demo}
*
* @example
* // A polyline with two connected line segments
* var polyline = new Cesium.PolylineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 0.0, 0.0,
* 5.0, 0.0,
* 5.0, 5.0
* ]),
* width : 10.0
* });
* var geometry = Cesium.PolylineGeometry.createGeometry(polyline);
*/
function PolylineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.positions;
var colors = options.colors;
var width = defaultValue(options.width, 1.0);
var colorsPerVertex = defaultValue(options.colorsPerVertex, false);
if ((!defined(positions)) || (positions.length < 2)) {
throw new DeveloperError('At least two positions are required.');
}
if (typeof width !== 'number') {
throw new DeveloperError('width must be a number');
}
if (defined(colors) && ((colorsPerVertex && colors.length < positions.length) || (!colorsPerVertex && colors.length < positions.length - 1))) {
throw new DeveloperError('colors has an invalid length.');
}
this._positions = positions;
this._colors = colors;
this._width = width;
this._colorsPerVertex = colorsPerVertex;
this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
this._followSurface = defaultValue(options.followSurface, true);
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
this._workerName = 'createPolylineGeometry';
var numComponents = 1 + positions.length * Cartesian3.packedLength;
numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 4;
}
/**
* Stores the provided instance into the provided array.
*
* @param {PolylineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
PolylineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
var colors = value._colors;
length = defined(colors) ? colors.length : 0.0;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
Color.pack(colors[i], array, startingIndex);
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
array[startingIndex++] = value._width;
array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0;
array[startingIndex++] = value._followSurface ? 1.0 : 0.0;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
positions : undefined,
colors : undefined,
ellipsoid : scratchEllipsoid,
vertexFormat : scratchVertexFormat,
width : undefined,
colorsPerVertex : undefined,
followSurface : undefined,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {PolylineGeometry} [result] The object into which to store the result.
* @returns {PolylineGeometry} The modified result parameter or a new PolylineGeometry instance if one was not provided.
*/
PolylineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var length = array[startingIndex++];
var positions = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
length = array[startingIndex++];
var colors = length > 0 ? new Array(length) : undefined;
for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
colors[i] = Color.unpack(array, startingIndex);
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var width = array[startingIndex++];
var colorsPerVertex = array[startingIndex++] === 1.0;
var followSurface = array[startingIndex++] === 1.0;
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.positions = positions;
scratchOptions.colors = colors;
scratchOptions.width = width;
scratchOptions.colorsPerVertex = colorsPerVertex;
scratchOptions.followSurface = followSurface;
scratchOptions.granularity = granularity;
return new PolylineGeometry(scratchOptions);
}
result._positions = positions;
result._colors = colors;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._width = width;
result._colorsPerVertex = colorsPerVertex;
result._followSurface = followSurface;
result._granularity = granularity;
return result;
};
var scratchCartesian3 = new Cartesian3();
var scratchPosition = new Cartesian3();
var scratchPrevPosition = new Cartesian3();
var scratchNextPosition = new Cartesian3();
/**
* Computes the geometric representation of a polyline, including its vertices, indices, and a bounding sphere.
*
* @param {PolylineGeometry} polylineGeometry A description of the polyline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
PolylineGeometry.createGeometry = function(polylineGeometry) {
var width = polylineGeometry._width;
var vertexFormat = polylineGeometry._vertexFormat;
var colors = polylineGeometry._colors;
var colorsPerVertex = polylineGeometry._colorsPerVertex;
var followSurface = polylineGeometry._followSurface;
var granularity = polylineGeometry._granularity;
var ellipsoid = polylineGeometry._ellipsoid;
var i;
var j;
var k;
var positions = arrayRemoveDuplicates(polylineGeometry._positions, Cartesian3.equalsEpsilon);
var positionsLength = positions.length;
// A width of a pixel or less is not a valid geometry, but in order to support external data
// that may have errors we treat this as an empty geometry.
if (positionsLength < 2 || width <= 0.0) {
return undefined;
}
if (followSurface) {
var heights = PolylinePipeline.extractHeights(positions, ellipsoid);
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
if (defined(colors)) {
var colorLength = 1;
for (i = 0; i < positionsLength - 1; ++i) {
colorLength += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance);
}
var newColors = new Array(colorLength);
var newColorIndex = 0;
for (i = 0; i < positionsLength - 1; ++i) {
var p0 = positions[i];
var p1 = positions[i+1];
var c0 = colors[i];
var numColors = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
if (colorsPerVertex && i < colorLength) {
var c1 = colors[i+1];
var interpolatedColors = interpolateColors(p0, p1, c0, c1, numColors);
var interpolatedColorsLength = interpolatedColors.length;
for (j = 0; j < interpolatedColorsLength; ++j) {
newColors[newColorIndex++] = interpolatedColors[j];
}
} else {
for (j = 0; j < numColors; ++j) {
newColors[newColorIndex++] = Color.clone(c0);
}
}
}
newColors[newColorIndex] = Color.clone(colors[colors.length-1]);
colors = newColors;
scratchInterpolateColorsArray.length = 0;
}
positions = PolylinePipeline.generateCartesianArc({
positions: positions,
minDistance: minDistance,
ellipsoid: ellipsoid,
height: heights
});
}
positionsLength = positions.length;
var size = positionsLength * 4.0 - 4.0;
var finalPositions = new Float64Array(size * 3);
var prevPositions = new Float64Array(size * 3);
var nextPositions = new Float64Array(size * 3);
var expandAndWidth = new Float32Array(size * 2);
var st = vertexFormat.st ? new Float32Array(size * 2) : undefined;
var finalColors = defined(colors) ? new Uint8Array(size * 4) : undefined;
var positionIndex = 0;
var expandAndWidthIndex = 0;
var stIndex = 0;
var colorIndex = 0;
var position;
for (j = 0; j < positionsLength; ++j) {
if (j === 0) {
position = scratchCartesian3;
Cartesian3.subtract(positions[0], positions[1], position);
Cartesian3.add(positions[0], position, position);
} else {
position = positions[j - 1];
}
Cartesian3.clone(position, scratchPrevPosition);
Cartesian3.clone(positions[j], scratchPosition);
if (j === positionsLength - 1) {
position = scratchCartesian3;
Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], position);
Cartesian3.add(positions[positionsLength - 1], position, position);
} else {
position = positions[j + 1];
}
Cartesian3.clone(position, scratchNextPosition);
var color0, color1;
if (defined(finalColors)) {
if (j !== 0 && !colorsPerVertex) {
color0 = colors[j - 1];
} else {
color0 = colors[j];
}
if (j !== positionsLength - 1) {
color1 = colors[j];
}
}
var startK = j === 0 ? 2 : 0;
var endK = j === positionsLength - 1 ? 2 : 4;
for (k = startK; k < endK; ++k) {
Cartesian3.pack(scratchPosition, finalPositions, positionIndex);
Cartesian3.pack(scratchPrevPosition, prevPositions, positionIndex);
Cartesian3.pack(scratchNextPosition, nextPositions, positionIndex);
positionIndex += 3;
var direction = (k - 2 < 0) ? -1.0 : 1.0;
expandAndWidth[expandAndWidthIndex++] = 2 * (k % 2) - 1; // expand direction
expandAndWidth[expandAndWidthIndex++] = direction * width;
if (vertexFormat.st) {
st[stIndex++] = j / (positionsLength - 1);
st[stIndex++] = Math.max(expandAndWidth[expandAndWidthIndex - 2], 0.0);
}
if (defined(finalColors)) {
var color = (k < 2) ? color0 : color1;
finalColors[colorIndex++] = Color.floatToByte(color.red);
finalColors[colorIndex++] = Color.floatToByte(color.green);
finalColors[colorIndex++] = Color.floatToByte(color.blue);
finalColors[colorIndex++] = Color.floatToByte(color.alpha);
}
}
}
var attributes = new GeometryAttributes();
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : finalPositions
});
attributes.prevPosition = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : prevPositions
});
attributes.nextPosition = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : nextPositions
});
attributes.expandAndWidth = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : expandAndWidth
});
if (vertexFormat.st) {
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : st
});
}
if (defined(finalColors)) {
attributes.color = new GeometryAttribute({
componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
componentsPerAttribute : 4,
values : finalColors,
normalize : true
});
}
var indices = IndexDatatype.createTypedArray(size, positionsLength * 6 - 6);
var index = 0;
var indicesIndex = 0;
var length = positionsLength - 1.0;
for (j = 0; j < length; ++j) {
indices[indicesIndex++] = index;
indices[indicesIndex++] = index + 2;
indices[indicesIndex++] = index + 1;
indices[indicesIndex++] = index + 1;
indices[indicesIndex++] = index + 2;
indices[indicesIndex++] = index + 3;
index += 4;
}
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : BoundingSphere.fromPoints(positions),
geometryType : GeometryType.POLYLINES
});
};
return PolylineGeometry;
});
/*global define*/
define('Core/PolylineVolumeGeometry',[
'./arrayRemoveDuplicates',
'./BoundingRectangle',
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./CornerType',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./GeometryPipeline',
'./IndexDatatype',
'./Math',
'./oneTimeWarning',
'./PolygonPipeline',
'./PolylineVolumeGeometryLibrary',
'./PrimitiveType',
'./VertexFormat',
'./WindingOrder'
], function(
arrayRemoveDuplicates,
BoundingRectangle,
BoundingSphere,
Cartesian2,
Cartesian3,
ComponentDatatype,
CornerType,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryPipeline,
IndexDatatype,
CesiumMath,
oneTimeWarning,
PolygonPipeline,
PolylineVolumeGeometryLibrary,
PrimitiveType,
VertexFormat,
WindingOrder) {
'use strict';
function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat) {
var attributes = new GeometryAttributes();
if (vertexFormat.position) {
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : combinedPositions
});
}
var shapeLength = shape.length;
var vertexCount = combinedPositions.length / 3;
var length = (vertexCount - shapeLength * 2) / (shapeLength * 2);
var firstEndIndices = PolygonPipeline.triangulate(shape);
var indicesCount = (length - 1) * (shapeLength) * 6 + firstEndIndices.length * 2;
var indices = IndexDatatype.createTypedArray(vertexCount, indicesCount);
var i, j;
var ll, ul, ur, lr;
var offset = shapeLength * 2;
var index = 0;
for (i = 0; i < length - 1; i++) {
for (j = 0; j < shapeLength - 1; j++) {
ll = j * 2 + i * shapeLength * 2;
lr = ll + offset;
ul = ll + 1;
ur = ul + offset;
indices[index++] = ul;
indices[index++] = ll;
indices[index++] = ur;
indices[index++] = ur;
indices[index++] = ll;
indices[index++] = lr;
}
ll = shapeLength * 2 - 2 + i * shapeLength * 2;
ul = ll + 1;
ur = ul + offset;
lr = ll + offset;
indices[index++] = ul;
indices[index++] = ll;
indices[index++] = ur;
indices[index++] = ur;
indices[index++] = ll;
indices[index++] = lr;
}
if (vertexFormat.st || vertexFormat.tangent || vertexFormat.binormal) { // st required for tangent/binormal calculation
var st = new Float32Array(vertexCount * 2);
var lengthSt = 1 / (length - 1);
var heightSt = 1 / (boundingRectangle.height);
var heightOffset = boundingRectangle.height / 2;
var s, t;
var stindex = 0;
for (i = 0; i < length; i++) {
s = i * lengthSt;
t = heightSt * (shape[0].y + heightOffset);
st[stindex++] = s;
st[stindex++] = t;
for (j = 1; j < shapeLength; j++) {
t = heightSt * (shape[j].y + heightOffset);
st[stindex++] = s;
st[stindex++] = t;
st[stindex++] = s;
st[stindex++] = t;
}
t = heightSt * (shape[0].y + heightOffset);
st[stindex++] = s;
st[stindex++] = t;
}
for (j = 0; j < shapeLength; j++) {
s = 0;
t = heightSt * (shape[j].y + heightOffset);
st[stindex++] = s;
st[stindex++] = t;
}
for (j = 0; j < shapeLength; j++) {
s = (length - 1) * lengthSt;
t = heightSt * (shape[j].y + heightOffset);
st[stindex++] = s;
st[stindex++] = t;
}
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : new Float32Array(st)
});
}
var endOffset = vertexCount - shapeLength * 2;
for (i = 0; i < firstEndIndices.length; i += 3) {
var v0 = firstEndIndices[i] + endOffset;
var v1 = firstEndIndices[i + 1] + endOffset;
var v2 = firstEndIndices[i + 2] + endOffset;
indices[index++] = v0;
indices[index++] = v1;
indices[index++] = v2;
indices[index++] = v2 + shapeLength;
indices[index++] = v1 + shapeLength;
indices[index++] = v0 + shapeLength;
}
var geometry = new Geometry({
attributes : attributes,
indices : indices,
boundingSphere : BoundingSphere.fromVertices(combinedPositions),
primitiveType : PrimitiveType.TRIANGLES
});
if (vertexFormat.normal) {
geometry = GeometryPipeline.computeNormal(geometry);
}
if (vertexFormat.tangent || vertexFormat.binormal) {
try {
geometry = GeometryPipeline.computeBinormalAndTangent(geometry);
} catch (e) {
oneTimeWarning('polyline-volume-tangent-binormal', 'Unable to compute tangents and binormals for polyline volume geometry');
//TODO https://github.com/AnalyticalGraphicsInc/cesium/issues/3609
}
if (!vertexFormat.tangent) {
geometry.attributes.tangent = undefined;
}
if (!vertexFormat.binormal) {
geometry.attributes.binormal = undefined;
}
if (!vertexFormat.st) {
geometry.attributes.st = undefined;
}
}
return geometry;
}
/**
* A description of a polyline with a volume (a 2D shape extruded along a polyline).
*
* @alias PolylineVolumeGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.polylinePositions An array of {@link Cartesain3} positions that define the center of the polyline volume.
* @param {Cartesian2[]} options.shapePositions An array of {@link Cartesian2} positions that define the shape to be extruded along the polyline
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
*
* @see PolylineVolumeGeometry#createGeometry
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo}
*
* @example
* function computeCircle(radius) {
* var positions = [];
* for (var i = 0; i < 360; i++) {
* var radians = Cesium.Math.toRadians(i);
* positions.push(new Cesium.Cartesian2(radius * Math.cos(radians), radius * Math.sin(radians)));
* }
* return positions;
* }
*
* var volume = new Cesium.PolylineVolumeGeometry({
* vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
* polylinePositions : Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0
* ]),
* shapePositions : computeCircle(100000.0)
* });
*/
function PolylineVolumeGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.polylinePositions;
var shape = options.shapePositions;
if (!defined(positions)) {
throw new DeveloperError('options.polylinePositions is required.');
}
if (!defined(shape)) {
throw new DeveloperError('options.shapePositions is required.');
}
this._positions = positions;
this._shape = shape;
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
this._workerName = 'createPolylineVolumeGeometry';
var numComponents = 1 + positions.length * Cartesian3.packedLength;
numComponents += 1 + shape.length * Cartesian2.packedLength;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 2;
}
/**
* Stores the provided instance into the provided array.
*
* @param {PolylineVolumeGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
PolylineVolumeGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
var shape = value._shape;
length = shape.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
Cartesian2.pack(shape[i], array, startingIndex);
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
array[startingIndex++] = value._cornerType;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
polylinePositions : undefined,
shapePositions : undefined,
ellipsoid : scratchEllipsoid,
vertexFormat : scratchVertexFormat,
cornerType : undefined,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {PolylineVolumeGeometry} [result] The object into which to store the result.
* @returns {PolylineVolumeGeometry} The modified result parameter or a new PolylineVolumeGeometry instance if one was not provided.
*/
PolylineVolumeGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var length = array[startingIndex++];
var positions = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
length = array[startingIndex++];
var shape = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
shape[i] = Cartesian2.unpack(array, startingIndex);
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var cornerType = array[startingIndex++];
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.polylinePositions = positions;
scratchOptions.shapePositions = shape;
scratchOptions.cornerType = cornerType;
scratchOptions.granularity = granularity;
return new PolylineVolumeGeometry(scratchOptions);
}
result._positions = positions;
result._shape = shape;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._cornerType = cornerType;
result._granularity = granularity;
return result;
};
var brScratch = new BoundingRectangle();
/**
* Computes the geometric representation of a polyline with a volume, including its vertices, indices, and a bounding sphere.
*
* @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polyline volume.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) {
var positions = polylineVolumeGeometry._positions;
var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
var shape2D = polylineVolumeGeometry._shape;
shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D);
if (cleanPositions.length < 2 || shape2D.length < 3) {
return undefined;
}
if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) {
shape2D.reverse();
}
var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch);
var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeGeometry, true);
return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeGeometry._vertexFormat);
};
return PolylineVolumeGeometry;
});
/*global define*/
define('Core/PolylineVolumeOutlineGeometry',[
'./arrayRemoveDuplicates',
'./BoundingRectangle',
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./ComponentDatatype',
'./CornerType',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PolygonPipeline',
'./PolylineVolumeGeometryLibrary',
'./PrimitiveType',
'./WindingOrder'
], function(
arrayRemoveDuplicates,
BoundingRectangle,
BoundingSphere,
Cartesian2,
Cartesian3,
ComponentDatatype,
CornerType,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PolygonPipeline,
PolylineVolumeGeometryLibrary,
PrimitiveType,
WindingOrder) {
'use strict';
function computeAttributes(positions, shape) {
var attributes = new GeometryAttributes();
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
var shapeLength = shape.length;
var vertexCount = attributes.position.values.length / 3;
var positionLength = positions.length / 3;
var shapeCount = positionLength / shapeLength;
var indices = IndexDatatype.createTypedArray(vertexCount, 2 * shapeLength * (shapeCount + 1));
var i, j;
var index = 0;
i = 0;
var offset = i * shapeLength;
for (j = 0; j < shapeLength - 1; j++) {
indices[index++] = j + offset;
indices[index++] = j + offset + 1;
}
indices[index++] = shapeLength - 1 + offset;
indices[index++] = offset;
i = shapeCount - 1;
offset = i * shapeLength;
for (j = 0; j < shapeLength - 1; j++) {
indices[index++] = j + offset;
indices[index++] = j + offset + 1;
}
indices[index++] = shapeLength - 1 + offset;
indices[index++] = offset;
for (i = 0; i < shapeCount - 1; i++) {
var firstOffset = shapeLength * i;
var secondOffset = firstOffset + shapeLength;
for (j = 0; j < shapeLength; j++) {
indices[index++] = j + firstOffset;
indices[index++] = j + secondOffset;
}
}
var geometry = new Geometry({
attributes : attributes,
indices : IndexDatatype.createTypedArray(vertexCount, indices),
boundingSphere : BoundingSphere.fromVertices(positions),
primitiveType : PrimitiveType.LINES
});
return geometry;
}
/**
* A description of a polyline with a volume (a 2D shape extruded along a polyline).
*
* @alias PolylineVolumeOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.polylinePositions An array of positions that define the center of the polyline volume.
* @param {Cartesian2[]} options.shapePositions An array of positions that define the shape to be extruded along the polyline
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
*
* @see PolylineVolumeOutlineGeometry#createGeometry
*
* @example
* function computeCircle(radius) {
* var positions = [];
* for (var i = 0; i < 360; i++) {
* var radians = Cesium.Math.toRadians(i);
* positions.push(new Cesium.Cartesian2(radius * Math.cos(radians), radius * Math.sin(radians)));
* }
* return positions;
* }
*
* var volumeOutline = new Cesium.PolylineVolumeOutlineGeometry({
* polylinePositions : Cesium.Cartesian3.fromDegreesArray([
* -72.0, 40.0,
* -70.0, 35.0
* ]),
* shapePositions : computeCircle(100000.0)
* });
*/
function PolylineVolumeOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.polylinePositions;
var shape = options.shapePositions;
if (!defined(positions)) {
throw new DeveloperError('options.polylinePositions is required.');
}
if (!defined(shape)) {
throw new DeveloperError('options.shapePositions is required.');
}
this._positions = positions;
this._shape = shape;
this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
this._workerName = 'createPolylineVolumeOutlineGeometry';
var numComponents = 1 + positions.length * Cartesian3.packedLength;
numComponents += 1 + shape.length * Cartesian2.packedLength;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = numComponents + Ellipsoid.packedLength + 2;
}
/**
* Stores the provided instance into the provided array.
*
* @param {PolylineVolumeOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
PolylineVolumeOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
var shape = value._shape;
length = shape.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
Cartesian2.pack(shape[i], array, startingIndex);
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex++] = value._cornerType;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchOptions = {
polylinePositions : undefined,
shapePositions : undefined,
ellipsoid : scratchEllipsoid,
height : undefined,
cornerType : undefined,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {PolylineVolumeOutlineGeometry} [result] The object into which to store the result.
* @returns {PolylineVolumeOutlineGeometry} The modified result parameter or a new PolylineVolumeOutlineGeometry instance if one was not provided.
*/
PolylineVolumeOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var length = array[startingIndex++];
var positions = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
length = array[startingIndex++];
var shape = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
shape[i] = Cartesian2.unpack(array, startingIndex);
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var cornerType = array[startingIndex++];
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.polylinePositions = positions;
scratchOptions.shapePositions = shape;
scratchOptions.cornerType = cornerType;
scratchOptions.granularity = granularity;
return new PolylineVolumeOutlineGeometry(scratchOptions);
}
result._positions = positions;
result._shape = shape;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._cornerType = cornerType;
result._granularity = granularity;
return result;
};
var brScratch = new BoundingRectangle();
/**
* Computes the geometric representation of the outline of a polyline with a volume, including its vertices, indices, and a bounding sphere.
*
* @param {PolylineVolumeOutlineGeometry} polylineVolumeOutlineGeometry A description of the polyline volume outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
PolylineVolumeOutlineGeometry.createGeometry = function(polylineVolumeOutlineGeometry) {
var positions = polylineVolumeOutlineGeometry._positions;
var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
var shape2D = polylineVolumeOutlineGeometry._shape;
shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D);
if (cleanPositions.length < 2 || shape2D.length < 3) {
return undefined;
}
if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) {
shape2D.reverse();
}
var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch);
var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry, false);
return computeAttributes(computedPositions, shape2D);
};
return PolylineVolumeOutlineGeometry;
});
/*global define*/
define('Core/QuaternionSpline',[
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Quaternion',
'./Spline'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Quaternion,
Spline) {
'use strict';
function computeInnerQuadrangles(points, firstInnerQuadrangle, lastInnerQuadrangle) {
var length = points.length;
var quads = new Array(length);
quads[0] = defined(firstInnerQuadrangle) ? firstInnerQuadrangle : points[0];
quads[length - 1] = defined(lastInnerQuadrangle) ? lastInnerQuadrangle : points[length - 1];
for (var i = 1; i < length - 1; ++i) {
quads[i] = Quaternion.computeInnerQuadrangle(points[i - 1], points[i], points[i + 1], new Quaternion());
}
return quads;
}
function createEvaluateFunction(spline) {
var points = spline.points;
var quads = spline.innerQuadrangles;
var times = spline.times;
// use slerp interpolation for 2 points
if (points.length < 3) {
var t0 = times[0];
var invSpan = 1.0 / (times[1] - t0);
var q0 = points[0];
var q1 = points[1];
return function(time, result) {
if (!defined(result)){
result = new Quaternion();
}
var u = (time - t0) * invSpan;
return Quaternion.fastSlerp(q0, q1, u, result);
};
}
// use quad interpolation for more than 3 points
return function(time, result) {
if (!defined(result)){
result = new Quaternion();
}
var i = spline._lastTimeIndex = spline.findTimeInterval(time, spline._lastTimeIndex);
var u = (time - times[i]) / (times[i + 1] - times[i]);
var q0 = points[i];
var q1 = points[i + 1];
var s0 = quads[i];
var s1 = quads[i + 1];
return Quaternion.fastSquad(q0, q1, s0, s1, u, result);
};
}
/**
* A spline that uses spherical quadrangle (squad) interpolation to create a quaternion curve.
* The generated curve is in the class C1 .
*
* @alias QuaternionSpline
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
* The values are in no way connected to the clock time. They are the parameterization for the curve.
* @param {Quaternion[]} options.points The array of {@link Quaternion} control points.
* @param {Quaternion} [options.firstInnerQuadrangle] The inner quadrangle of the curve at the first control point.
* If the inner quadrangle is not given, it will be estimated.
* @param {Quaternion} [options.lastInnerQuadrangle] The inner quadrangle of the curve at the last control point.
* If the inner quadrangle is not given, it will be estimated.
*
* @exception {DeveloperError} points.length must be greater than or equal to 2.
* @exception {DeveloperError} times.length must be equal to points.length.
*
* @see HermiteSpline
* @see CatmullRomSpline
* @see LinearSpline
*/
function QuaternionSpline(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var points = options.points;
var times = options.times;
var firstInnerQuadrangle = options.firstInnerQuadrangle;
var lastInnerQuadrangle = options.lastInnerQuadrangle;
if (!defined(points) || !defined(times)) {
throw new DeveloperError('points and times are required.');
}
if (points.length < 2) {
throw new DeveloperError('points.length must be greater than or equal to 2.');
}
if (times.length !== points.length) {
throw new DeveloperError('times.length must be equal to points.length.');
}
var innerQuadrangles = computeInnerQuadrangles(points, firstInnerQuadrangle, lastInnerQuadrangle);
this._times = times;
this._points = points;
this._innerQuadrangles = innerQuadrangles;
this._evaluateFunction = createEvaluateFunction(this);
this._lastTimeIndex = 0;
}
defineProperties(QuaternionSpline.prototype, {
/**
* An array of times for the control points.
*
* @memberof QuaternionSpline.prototype
*
* @type {Number[]}
* @readonly
*/
times : {
get : function() {
return this._times;
}
},
/**
* An array of {@link Quaternion} control points.
*
* @memberof QuaternionSpline.prototype
*
* @type {Quaternion[]}
* @readonly
*/
points : {
get : function() {
return this._points;
}
},
/**
* An array of {@link Quaternion} inner quadrangles for the control points.
*
* @memberof QuaternionSpline.prototype
*
* @type {Quaternion[]}
* @readonly
*/
innerQuadrangles : {
get : function() {
return this._innerQuadrangles;
}
}
});
/**
* Finds an index i
in times
such that the parameter
* time
is in the interval [times[i], times[i + 1]]
.
* @function
*
* @param {Number} time The time.
* @returns {Number} The index for the element at the start of the interval.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
QuaternionSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;
/**
* Evaluates the curve at a given time.
*
* @param {Number} time The time at which to evaluate the curve.
* @param {Quaternion} [result] The object onto which to store the result.
* @returns {Quaternion} The modified result parameter or a new instance of the point on the curve at the given time.
*
* @exception {DeveloperError} time must be in the range [t0 , tn ]
, where t0
* is the first element in the array times
and tn
is the last element
* in the array times
.
*/
QuaternionSpline.prototype.evaluate = function(time, result) {
return this._evaluateFunction(time, result);
};
return QuaternionSpline;
});
/*global define*/
define('Core/RectangleGeometryLibrary',[
'./Cartesian3',
'./Cartographic',
'./defined',
'./DeveloperError',
'./GeographicProjection',
'./Math',
'./Matrix2',
'./Rectangle'
], function(
Cartesian3,
Cartographic,
defined,
DeveloperError,
GeographicProjection,
CesiumMath,
Matrix2,
Rectangle) {
'use strict';
var cos = Math.cos;
var sin = Math.sin;
var sqrt = Math.sqrt;
/**
* @private
*/
var RectangleGeometryLibrary = {};
/**
* @private
*/
RectangleGeometryLibrary.computePosition = function(options, row, col, position, st) {
var radiiSquared = options.ellipsoid.radiiSquared;
var nwCorner = options.nwCorner;
var rectangle = options.rectangle;
var stLatitude = nwCorner.latitude - options.granYCos * row + col * options.granXSin;
var cosLatitude = cos(stLatitude);
var nZ = sin(stLatitude);
var kZ = radiiSquared.z * nZ;
var stLongitude = nwCorner.longitude + row * options.granYSin + col * options.granXCos;
var nX = cosLatitude * cos(stLongitude);
var nY = cosLatitude * sin(stLongitude);
var kX = radiiSquared.x * nX;
var kY = radiiSquared.y * nY;
var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ));
position.x = kX / gamma;
position.y = kY / gamma;
position.z = kZ / gamma;
if (defined(options.vertexFormat) && options.vertexFormat.st) {
var stNwCorner = options.stNwCorner;
if (defined(stNwCorner)) {
stLatitude = stNwCorner.latitude - options.stGranYCos * row + col * options.stGranXSin;
stLongitude = stNwCorner.longitude + row * options.stGranYSin + col * options.stGranXCos;
st.x = (stLongitude - options.stWest) * options.lonScalar;
st.y = (stLatitude - options.stSouth) * options.latScalar;
} else {
st.x = (stLongitude - rectangle.west) * options.lonScalar;
st.y = (stLatitude - rectangle.south) * options.latScalar;
}
}
};
var rotationMatrixScratch = new Matrix2();
var nwCartesian = new Cartesian3();
var centerScratch = new Cartographic();
var centerCartesian = new Cartesian3();
var proj = new GeographicProjection();
function getRotationOptions(nwCorner, rotation, granularityX, granularityY, center, width, height) {
var cosRotation = Math.cos(rotation);
var granYCos = granularityY * cosRotation;
var granXCos = granularityX * cosRotation;
var sinRotation = Math.sin(rotation);
var granYSin = granularityY * sinRotation;
var granXSin = granularityX * sinRotation;
nwCartesian = proj.project(nwCorner, nwCartesian);
nwCartesian = Cartesian3.subtract(nwCartesian, centerCartesian, nwCartesian);
var rotationMatrix = Matrix2.fromRotation(rotation, rotationMatrixScratch);
nwCartesian = Matrix2.multiplyByVector(rotationMatrix, nwCartesian, nwCartesian);
nwCartesian = Cartesian3.add(nwCartesian, centerCartesian, nwCartesian);
nwCorner = proj.unproject(nwCartesian, nwCorner);
width -= 1;
height -= 1;
var latitude = nwCorner.latitude;
var latitude0 = latitude + width * granXSin;
var latitude1 = latitude - granYCos * height;
var latitude2 = latitude - granYCos * height + width * granXSin;
var north = Math.max(latitude, latitude0, latitude1, latitude2);
var south = Math.min(latitude, latitude0, latitude1, latitude2);
var longitude = nwCorner.longitude;
var longitude0 = longitude + width * granXCos;
var longitude1 = longitude + height * granYSin;
var longitude2 = longitude + height * granYSin + width * granXCos;
var east = Math.max(longitude, longitude0, longitude1, longitude2);
var west = Math.min(longitude, longitude0, longitude1, longitude2);
return {
north: north,
south: south,
east: east,
west: west,
granYCos : granYCos,
granYSin : granYSin,
granXCos : granXCos,
granXSin : granXSin,
nwCorner : nwCorner
};
}
/**
* @private
*/
RectangleGeometryLibrary.computeOptions = function(geometry, rectangle, nwCorner, stNwCorner) {
var granularity = geometry._granularity;
var ellipsoid = geometry._ellipsoid;
var surfaceHeight = geometry._surfaceHeight;
var rotation = geometry._rotation;
var stRotation = geometry._stRotation;
var extrudedHeight = geometry._extrudedHeight;
var east = rectangle.east;
var west = rectangle.west;
var north = rectangle.north;
var south = rectangle.south;
var width;
var height;
var granularityX;
var granularityY;
var dx;
var dy = north - south;
if (west > east) {
dx = (CesiumMath.TWO_PI - west + east);
width = Math.ceil(dx / granularity) + 1;
height = Math.ceil(dy / granularity) + 1;
granularityX = dx / (width - 1);
granularityY = dy / (height - 1);
} else {
dx = east - west;
width = Math.ceil(dx / granularity) + 1;
height = Math.ceil(dy / granularity) + 1;
granularityX = dx / (width - 1);
granularityY = dy / (height - 1);
}
nwCorner = Rectangle.northwest(rectangle, nwCorner);
var center = Rectangle.center(rectangle, centerScratch);
if (rotation !== 0 || stRotation !== 0) {
if (center.longitude < nwCorner.longitude) {
center.longitude += CesiumMath.TWO_PI;
}
centerCartesian = proj.project(center, centerCartesian);
}
var granYCos = granularityY;
var granXCos = granularityX;
var granYSin = 0.0;
var granXSin = 0.0;
var options = {
granYCos : granYCos,
granYSin : granYSin,
granXCos : granXCos,
granXSin : granXSin,
ellipsoid : ellipsoid,
surfaceHeight : surfaceHeight,
extrudedHeight : extrudedHeight,
nwCorner : nwCorner,
rectangle : rectangle,
width: width,
height: height
};
if (rotation !== 0) {
var rotationOptions = getRotationOptions(nwCorner, rotation, granularityX, granularityY, center, width, height);
north = rotationOptions.north;
south = rotationOptions.south;
east = rotationOptions.east;
west = rotationOptions.west;
if (north < -CesiumMath.PI_OVER_TWO || north > CesiumMath.PI_OVER_TWO ||
south < -CesiumMath.PI_OVER_TWO || south > CesiumMath.PI_OVER_TWO) {
throw new DeveloperError('Rotated rectangle is invalid. It crosses over either the north or south pole.');
}
options.granYCos = rotationOptions.granYCos;
options.granYSin = rotationOptions.granYSin;
options.granXCos = rotationOptions.granXCos;
options.granXSin = rotationOptions.granXSin;
rectangle.north = north;
rectangle.south = south;
rectangle.east = east;
rectangle.west = west;
}
if (stRotation !== 0) {
rotation = rotation - stRotation;
stNwCorner = Rectangle.northwest(rectangle, stNwCorner);
var stRotationOptions = getRotationOptions(stNwCorner, rotation, granularityX, granularityY, center, width, height);
options.stGranYCos = stRotationOptions.granYCos;
options.stGranXCos = stRotationOptions.granXCos;
options.stGranYSin = stRotationOptions.granYSin;
options.stGranXSin = stRotationOptions.granXSin;
options.stNwCorner = stNwCorner;
options.stWest = stRotationOptions.west;
options.stSouth = stRotationOptions.south;
}
return options;
};
return RectangleGeometryLibrary;
});
/*global define*/
define('Core/RectangleGeometry',[
'./BoundingSphere',
'./Cartesian2',
'./Cartesian3',
'./Cartographic',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./GeometryInstance',
'./GeometryPipeline',
'./IndexDatatype',
'./Math',
'./Matrix2',
'./Matrix3',
'./PolygonPipeline',
'./PrimitiveType',
'./Quaternion',
'./Rectangle',
'./RectangleGeometryLibrary',
'./VertexFormat'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartographic,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryInstance,
GeometryPipeline,
IndexDatatype,
CesiumMath,
Matrix2,
Matrix3,
PolygonPipeline,
PrimitiveType,
Quaternion,
Rectangle,
RectangleGeometryLibrary,
VertexFormat) {
'use strict';
var positionScratch = new Cartesian3();
var normalScratch = new Cartesian3();
var tangentScratch = new Cartesian3();
var binormalScratch = new Cartesian3();
var rectangleScratch = new Rectangle();
var stScratch = new Cartesian2();
var bottomBoundingSphere = new BoundingSphere();
var topBoundingSphere = new BoundingSphere();
function createAttributes(vertexFormat, attributes) {
var geo = new Geometry({
attributes : new GeometryAttributes(),
primitiveType : PrimitiveType.TRIANGLES
});
geo.attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : attributes.positions
});
if (vertexFormat.normal) {
geo.attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : attributes.normals
});
}
if (vertexFormat.tangent) {
geo.attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : attributes.tangents
});
}
if (vertexFormat.binormal) {
geo.attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : attributes.binormals
});
}
return geo;
}
function calculateAttributes(positions, vertexFormat, ellipsoid, tangentRotationMatrix) {
var length = positions.length;
var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(length) : undefined;
var attrIndex = 0;
var binormal = binormalScratch;
var tangent = tangentScratch;
var normal = normalScratch;
for (var i = 0; i < length; i += 3) {
var p = Cartesian3.fromArray(positions, i, positionScratch);
var attrIndex1 = attrIndex + 1;
var attrIndex2 = attrIndex + 2;
if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
normal = ellipsoid.geodeticSurfaceNormal(p, normal);
if (vertexFormat.tangent || vertexFormat.binormal) {
Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent);
Matrix3.multiplyByVector(tangentRotationMatrix, tangent, tangent);
Cartesian3.normalize(tangent, tangent);
if (vertexFormat.binormal) {
Cartesian3.normalize(Cartesian3.cross(normal, tangent, binormal), binormal);
}
}
if (vertexFormat.normal) {
normals[attrIndex] = normal.x;
normals[attrIndex1] = normal.y;
normals[attrIndex2] = normal.z;
}
if (vertexFormat.tangent) {
tangents[attrIndex] = tangent.x;
tangents[attrIndex1] = tangent.y;
tangents[attrIndex2] = tangent.z;
}
if (vertexFormat.binormal) {
binormals[attrIndex] = binormal.x;
binormals[attrIndex1] = binormal.y;
binormals[attrIndex2] = binormal.z;
}
}
attrIndex += 3;
}
return createAttributes(vertexFormat, {
positions : positions,
normals : normals,
tangents : tangents,
binormals : binormals
});
}
var v1Scratch = new Cartesian3();
var v2Scratch = new Cartesian3();
function calculateAttributesWall(positions, vertexFormat, ellipsoid) {
var length = positions.length;
var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(length) : undefined;
var normalIndex = 0;
var tangentIndex = 0;
var binormalIndex = 0;
var recomputeNormal = true;
var binormal = binormalScratch;
var tangent = tangentScratch;
var normal = normalScratch;
for (var i = 0; i < length; i += 6) {
var p = Cartesian3.fromArray(positions, i, positionScratch);
if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
var p1 = Cartesian3.fromArray(positions, (i + 6) % length, v1Scratch);
if (recomputeNormal) {
var p2 = Cartesian3.fromArray(positions, (i + 3) % length, v2Scratch);
Cartesian3.subtract(p1, p, p1);
Cartesian3.subtract(p2, p, p2);
normal = Cartesian3.normalize(Cartesian3.cross(p2, p1, normal), normal);
recomputeNormal = false;
}
if (Cartesian3.equalsEpsilon(p1, p, CesiumMath.EPSILON10)) { // if we've reached a corner
recomputeNormal = true;
}
if (vertexFormat.tangent || vertexFormat.binormal) {
binormal = ellipsoid.geodeticSurfaceNormal(p, binormal);
if (vertexFormat.tangent) {
tangent = Cartesian3.normalize(Cartesian3.cross(binormal, normal, tangent), tangent);
}
}
if (vertexFormat.normal) {
normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;
normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;
}
if (vertexFormat.tangent) {
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
}
if (vertexFormat.binormal) {
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
}
}
}
return createAttributes(vertexFormat, {
positions : positions,
normals : normals,
tangents : tangents,
binormals : binormals
});
}
function constructRectangle(options) {
var vertexFormat = options.vertexFormat;
var ellipsoid = options.ellipsoid;
var size = options.size;
var height = options.height;
var width = options.width;
var positions = (vertexFormat.position) ? new Float64Array(size * 3) : undefined;
var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined;
var posIndex = 0;
var stIndex = 0;
var position = positionScratch;
var st = stScratch;
var minX = Number.MAX_VALUE;
var minY = Number.MAX_VALUE;
var maxX = -Number.MAX_VALUE;
var maxY = -Number.MAX_VALUE;
for (var row = 0; row < height; ++row) {
for (var col = 0; col < width; ++col) {
RectangleGeometryLibrary.computePosition(options, row, col, position, st);
positions[posIndex++] = position.x;
positions[posIndex++] = position.y;
positions[posIndex++] = position.z;
if (vertexFormat.st) {
textureCoordinates[stIndex++] = st.x;
textureCoordinates[stIndex++] = st.y;
minX = Math.min(minX, st.x);
minY = Math.min(minY, st.y);
maxX = Math.max(maxX, st.x);
maxY = Math.max(maxY, st.y);
}
}
}
if (vertexFormat.st && (minX < 0.0 || minY < 0.0 || maxX > 1.0 || maxY > 1.0)) {
for (var k = 0; k < textureCoordinates.length; k += 2) {
textureCoordinates[k] = (textureCoordinates[k] - minX) / (maxX - minX);
textureCoordinates[k + 1] = (textureCoordinates[k + 1] - minY) / (maxY - minY);
}
}
var geo = calculateAttributes(positions, vertexFormat, ellipsoid, options.tangentRotationMatrix);
var indicesSize = 6 * (width - 1) * (height - 1);
var indices = IndexDatatype.createTypedArray(size, indicesSize);
var index = 0;
var indicesIndex = 0;
for (var i = 0; i < height - 1; ++i) {
for (var j = 0; j < width - 1; ++j) {
var upperLeft = index;
var lowerLeft = upperLeft + width;
var lowerRight = lowerLeft + 1;
var upperRight = upperLeft + 1;
indices[indicesIndex++] = upperLeft;
indices[indicesIndex++] = lowerLeft;
indices[indicesIndex++] = upperRight;
indices[indicesIndex++] = upperRight;
indices[indicesIndex++] = lowerLeft;
indices[indicesIndex++] = lowerRight;
++index;
}
++index;
}
geo.indices = indices;
if (vertexFormat.st) {
geo.attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : textureCoordinates
});
}
return geo;
}
function addWallPositions(wallPositions, posIndex, i, topPositions, bottomPositions) {
wallPositions[posIndex++] = topPositions[i];
wallPositions[posIndex++] = topPositions[i + 1];
wallPositions[posIndex++] = topPositions[i + 2];
wallPositions[posIndex++] = bottomPositions[i];
wallPositions[posIndex++] = bottomPositions[i + 1];
wallPositions[posIndex++] = bottomPositions[i + 2];
return wallPositions;
}
function addWallTextureCoordinates(wallTextures, stIndex, i, st) {
wallTextures[stIndex++] = st[i];
wallTextures[stIndex++] = st[i + 1];
wallTextures[stIndex++] = st[i];
wallTextures[stIndex++] = st[i + 1];
return wallTextures;
}
function constructExtrudedRectangle(options) {
var vertexFormat = options.vertexFormat;
var surfaceHeight = options.surfaceHeight;
var extrudedHeight = options.extrudedHeight;
var minHeight = Math.min(extrudedHeight, surfaceHeight);
var maxHeight = Math.max(extrudedHeight, surfaceHeight);
var height = options.height;
var width = options.width;
var ellipsoid = options.ellipsoid;
var i;
var topBottomGeo = constructRectangle(options);
if (CesiumMath.equalsEpsilon(minHeight, maxHeight, CesiumMath.EPSILON10)) {
return topBottomGeo;
}
var topPositions = PolygonPipeline.scaleToGeodeticHeight(topBottomGeo.attributes.position.values, maxHeight, ellipsoid, false);
topPositions = new Float64Array(topPositions);
var length = topPositions.length;
var newLength = length*2;
var positions = new Float64Array(newLength);
positions.set(topPositions);
var bottomPositions = PolygonPipeline.scaleToGeodeticHeight(topBottomGeo.attributes.position.values, minHeight, ellipsoid);
positions.set(bottomPositions, length);
topBottomGeo.attributes.position.values = positions;
var normals = (vertexFormat.normal) ? new Float32Array(newLength) : undefined;
var tangents = (vertexFormat.tangent) ? new Float32Array(newLength) : undefined;
var binormals = (vertexFormat.binormal) ? new Float32Array(newLength) : undefined;
var textures = (vertexFormat.st) ? new Float32Array(newLength/3*2) : undefined;
var topSt;
if (vertexFormat.normal) {
var topNormals = topBottomGeo.attributes.normal.values;
normals.set(topNormals);
for (i = 0; i < length; i ++) {
topNormals[i] = -topNormals[i];
}
normals.set(topNormals, length);
topBottomGeo.attributes.normal.values = normals;
}
if (vertexFormat.tangent) {
var topTangents = topBottomGeo.attributes.tangent.values;
tangents.set(topTangents);
for (i = 0; i < length; i ++) {
topTangents[i] = -topTangents[i];
}
tangents.set(topTangents, length);
topBottomGeo.attributes.tangent.values = tangents;
}
if (vertexFormat.binormal) {
var topBinormals = topBottomGeo.attributes.binormal.values;
binormals.set(topBinormals);
binormals.set(topBinormals, length);
topBottomGeo.attributes.binormal.values = binormals;
}
if (vertexFormat.st) {
topSt = topBottomGeo.attributes.st.values;
textures.set(topSt);
textures.set(topSt, length/3*2);
topBottomGeo.attributes.st.values = textures;
}
var indices = topBottomGeo.indices;
var indicesLength = indices.length;
var posLength = length / 3;
var newIndices = IndexDatatype.createTypedArray(newLength/3, indicesLength*2);
newIndices.set(indices);
for (i = 0; i < indicesLength; i += 3) {
newIndices[i + indicesLength] = indices[i + 2] + posLength;
newIndices[i + 1 + indicesLength] = indices[i + 1] + posLength;
newIndices[i + 2 + indicesLength] = indices[i] + posLength;
}
topBottomGeo.indices = newIndices;
var perimeterPositions = 2 * width + 2 * height - 4;
var wallCount = (perimeterPositions + 4) * 2;
var wallPositions = new Float64Array(wallCount * 3);
var wallTextures = (vertexFormat.st) ? new Float32Array(wallCount * 2) : undefined;
var posIndex = 0;
var stIndex = 0;
var area = width * height;
for (i = 0; i < area; i+=width) {
wallPositions = addWallPositions(wallPositions, posIndex, i*3, topPositions, bottomPositions);
posIndex += 6;
if (vertexFormat.st) {
wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i*2, topSt);
stIndex += 4;
}
}
for (i = area-width; i < area; i++) {
wallPositions = addWallPositions(wallPositions, posIndex, i*3, topPositions, bottomPositions);
posIndex += 6;
if (vertexFormat.st) {
wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i*2, topSt);
stIndex += 4;
}
}
for (i = area-1; i > 0; i-=width) {
wallPositions = addWallPositions(wallPositions, posIndex, i*3, topPositions, bottomPositions);
posIndex += 6;
if (vertexFormat.st) {
wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i*2, topSt);
stIndex += 4;
}
}
for (i = width-1; i >= 0; i--) {
wallPositions = addWallPositions(wallPositions, posIndex, i*3, topPositions, bottomPositions);
posIndex += 6;
if (vertexFormat.st) {
wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i*2, topSt);
stIndex += 4;
}
}
var geo = calculateAttributesWall(wallPositions, vertexFormat, ellipsoid);
if (vertexFormat.st) {
geo.attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : wallTextures
});
}
var wallIndices = IndexDatatype.createTypedArray(wallCount, perimeterPositions * 6);
var upperLeft;
var lowerLeft;
var lowerRight;
var upperRight;
length = wallPositions.length / 3;
var index = 0;
for (i = 0; i < length - 1; i+=2) {
upperLeft = i;
upperRight = (upperLeft + 2) % length;
var p1 = Cartesian3.fromArray(wallPositions, upperLeft * 3, v1Scratch);
var p2 = Cartesian3.fromArray(wallPositions, upperRight * 3, v2Scratch);
if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON10)) {
continue;
}
lowerLeft = (upperLeft + 1) % length;
lowerRight = (lowerLeft + 2) % length;
wallIndices[index++] = upperLeft;
wallIndices[index++] = lowerLeft;
wallIndices[index++] = upperRight;
wallIndices[index++] = upperRight;
wallIndices[index++] = lowerLeft;
wallIndices[index++] = lowerRight;
}
geo.indices = wallIndices;
geo = GeometryPipeline.combineInstances([
new GeometryInstance({
geometry : topBottomGeo
}),
new GeometryInstance({
geometry : geo
})
]);
return geo[0];
}
var scratchRotationMatrix = new Matrix3();
var scratchCartesian3 = new Cartesian3();
var scratchQuaternion = new Quaternion();
var scratchRectanglePoints = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
var scratchCartographicPoints = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
function computeRectangle(rectangle, ellipsoid, rotation) {
if (rotation === 0.0) {
return Rectangle.clone(rectangle);
}
Rectangle.northeast(rectangle, scratchCartographicPoints[0]);
Rectangle.northwest(rectangle, scratchCartographicPoints[1]);
Rectangle.southeast(rectangle, scratchCartographicPoints[2]);
Rectangle.southwest(rectangle, scratchCartographicPoints[3]);
ellipsoid.cartographicArrayToCartesianArray(scratchCartographicPoints, scratchRectanglePoints);
var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.center(rectangle, scratchCartesian3));
Quaternion.fromAxisAngle(surfaceNormal, rotation, scratchQuaternion);
Matrix3.fromQuaternion(scratchQuaternion, scratchRotationMatrix);
for (var i = 0; i < 4; ++i) {
// Apply the rotation
Matrix3.multiplyByVector(scratchRotationMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]);
}
ellipsoid.cartesianArrayToCartographicArray(scratchRectanglePoints, scratchCartographicPoints);
return Rectangle.fromCartographicArray(scratchCartographicPoints);
}
/**
* A description of a cartographic rectangle on an ellipsoid centered at the origin. Rectangle geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
*
* @alias RectangleGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Rectangle} options.rectangle A cartographic rectangle with north, south, east and west properties in radians.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle lies.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Number} [options.height=0.0] The distance in meters between the rectangle and the ellipsoid surface.
* @param {Number} [options.rotation=0.0] The rotation of the rectangle, in radians. A positive rotation is counter-clockwise.
* @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
* @param {Number} [options.extrudedHeight] The distance in meters between the rectangle's extruded face and the ellipsoid surface.
* @param {Boolean} [options.closeTop=true] Specifies whether the rectangle has a top cover when extruded.
* @param {Boolean} [options.closeBottom=true] Specifies whether the rectangle has a bottom cover when extruded.
*
* @exception {DeveloperError} options.rectangle.north
must be in the interval [-Pi/2
, Pi/2
].
* @exception {DeveloperError} options.rectangle.south
must be in the interval [-Pi/2
, Pi/2
].
* @exception {DeveloperError} options.rectangle.east
must be in the interval [-Pi
, Pi
].
* @exception {DeveloperError} options.rectangle.west
must be in the interval [-Pi
, Pi
].
* @exception {DeveloperError} options.rectangle.north
must be greater than options.rectangle.south
.
*
* @see RectangleGeometry#createGeometry
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo}
*
* @example
* // 1. create an rectangle
* var rectangle = new Cesium.RectangleGeometry({
* ellipsoid : Cesium.Ellipsoid.WGS84,
* rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
* height : 10000.0
* });
* var geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
*
* // 2. create an extruded rectangle without a top
* var rectangle = new Cesium.RectangleGeometry({
* ellipsoid : Cesium.Ellipsoid.WGS84,
* rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
* height : 10000.0,
* extrudedHeight: 300000,
* closeTop: false
* });
* var geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
*/
function RectangleGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var rectangle = options.rectangle;
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var surfaceHeight = defaultValue(options.height, 0.0);
var rotation = defaultValue(options.rotation, 0.0);
var stRotation = defaultValue(options.stRotation, 0.0);
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
var extrudedHeight = options.extrudedHeight;
var extrude = defined(extrudedHeight);
var closeTop = defaultValue(options.closeTop, true);
var closeBottom = defaultValue(options.closeBottom, true);
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
Rectangle.validate(rectangle);
if (rectangle.north < rectangle.south) {
throw new DeveloperError('options.rectangle.north must be greater than options.rectangle.south');
}
this._rectangle = rectangle;
this._granularity = granularity;
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._surfaceHeight = surfaceHeight;
this._rotation = rotation;
this._stRotation = stRotation;
this._vertexFormat = VertexFormat.clone(vertexFormat);
this._extrudedHeight = defaultValue(extrudedHeight, 0.0);
this._extrude = extrude;
this._closeTop = closeTop;
this._closeBottom = closeBottom;
this._workerName = 'createRectangleGeometry';
this._rotatedRectangle = computeRectangle(this._rectangle, this._ellipsoid, rotation);
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
RectangleGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 8;
/**
* Stores the provided instance into the provided array.
*
* @param {RectangleGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
RectangleGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Rectangle.pack(value._rectangle, array, startingIndex);
startingIndex += Rectangle.packedLength;
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
Rectangle.pack(value._rotatedRectangle, array, startingIndex);
startingIndex += Rectangle.packedLength;
array[startingIndex++] = value._granularity;
array[startingIndex++] = value._surfaceHeight;
array[startingIndex++] = value._rotation;
array[startingIndex++] = value._stRotation;
array[startingIndex++] = value._extrudedHeight;
array[startingIndex++] = value._extrude ? 1.0 : 0.0;
array[startingIndex++] = value._closeTop ? 1.0 : 0.0;
array[startingIndex] = value._closeBottom ? 1.0 : 0.0;
return array;
};
var scratchRectangle = new Rectangle();
var scratchRotatedRectangle = new Rectangle();
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
rectangle : scratchRectangle,
ellipsoid : scratchEllipsoid,
vertexFormat : scratchVertexFormat,
granularity : undefined,
height : undefined,
rotation : undefined,
stRotation : undefined,
extrudedHeight : undefined,
closeTop : undefined,
closeBottom : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {RectangleGeometry} [result] The object into which to store the result.
* @returns {RectangleGeometry} The modified result parameter or a new RectangleGeometry instance if one was not provided.
*/
RectangleGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
startingIndex += Rectangle.packedLength;
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var rotatedRectangle = Rectangle.unpack(array, startingIndex, scratchRotatedRectangle);
startingIndex += Rectangle.packedLength;
var granularity = array[startingIndex++];
var surfaceHeight = array[startingIndex++];
var rotation = array[startingIndex++];
var stRotation = array[startingIndex++];
var extrudedHeight = array[startingIndex++];
var extrude = array[startingIndex++] === 1.0;
var closeTop = array[startingIndex++] === 1.0;
var closeBottom = array[startingIndex] === 1.0;
if (!defined(result)) {
scratchOptions.granularity = granularity;
scratchOptions.height = surfaceHeight;
scratchOptions.rotation = rotation;
scratchOptions.stRotation = stRotation;
scratchOptions.extrudedHeight = extrude ? extrudedHeight : undefined;
scratchOptions.closeTop = closeTop;
scratchOptions.closeBottom = closeBottom;
return new RectangleGeometry(scratchOptions);
}
result._rectangle = Rectangle.clone(rectangle, result._rectangle);
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._granularity = granularity;
result._surfaceHeight = surfaceHeight;
result._rotation = rotation;
result._stRotation = stRotation;
result._extrudedHeight = extrude ? extrudedHeight : undefined;
result._extrude = extrude;
result._closeTop = closeTop;
result._closeBottom = closeBottom;
result._rotatedRectangle = rotatedRectangle;
return result;
};
var tangentRotationMatrixScratch = new Matrix3();
var nwScratch = new Cartographic();
var stNwScratch = new Cartographic();
var quaternionScratch = new Quaternion();
var centerScratch = new Cartographic();
/**
* Computes the geometric representation of an rectangle, including its vertices, indices, and a bounding sphere.
*
* @param {RectangleGeometry} rectangleGeometry A description of the rectangle.
* @returns {Geometry|undefined} The computed vertices and indices.
*
* @exception {DeveloperError} Rotated rectangle is invalid.
*/
RectangleGeometry.createGeometry = function(rectangleGeometry) {
if ((CesiumMath.equalsEpsilon(rectangleGeometry._rectangle.north, rectangleGeometry._rectangle.south, CesiumMath.EPSILON10) ||
(CesiumMath.equalsEpsilon(rectangleGeometry._rectangle.east, rectangleGeometry._rectangle.west, CesiumMath.EPSILON10)))) {
return undefined;
}
var rectangle = Rectangle.clone(rectangleGeometry._rectangle, rectangleScratch);
var ellipsoid = rectangleGeometry._ellipsoid;
var surfaceHeight = rectangleGeometry._surfaceHeight;
var extrude = rectangleGeometry._extrude;
var extrudedHeight = rectangleGeometry._extrudedHeight;
var rotation = rectangleGeometry._rotation;
var stRotation = rectangleGeometry._stRotation;
var vertexFormat = rectangleGeometry._vertexFormat;
var options = RectangleGeometryLibrary.computeOptions(rectangleGeometry, rectangle, nwScratch, stNwScratch);
var tangentRotationMatrix = tangentRotationMatrixScratch;
if (stRotation !== 0 || rotation !== 0) {
var center = Rectangle.center(rectangle, centerScratch);
var axis = ellipsoid.geodeticSurfaceNormalCartographic(center, v1Scratch);
Quaternion.fromAxisAngle(axis, -stRotation, quaternionScratch);
Matrix3.fromQuaternion(quaternionScratch, tangentRotationMatrix);
} else {
Matrix3.clone(Matrix3.IDENTITY, tangentRotationMatrix);
}
options.lonScalar = 1.0 / rectangleGeometry._rectangle.width;
options.latScalar = 1.0 / rectangleGeometry._rectangle.height;
options.vertexFormat = vertexFormat;
options.rotation = rotation;
options.stRotation = stRotation;
options.tangentRotationMatrix = tangentRotationMatrix;
options.size = options.width * options.height;
var geometry;
var boundingSphere;
rectangle = rectangleGeometry._rectangle;
if (extrude) {
geometry = constructExtrudedRectangle(options);
var topBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight, topBoundingSphere);
var bottomBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, extrudedHeight, bottomBoundingSphere);
boundingSphere = BoundingSphere.union(topBS, bottomBS);
} else {
geometry = constructRectangle(options);
geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.attributes.position.values, surfaceHeight, ellipsoid, false);
boundingSphere = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight);
}
if (!vertexFormat.position) {
delete geometry.attributes.position;
}
return new Geometry({
attributes : new GeometryAttributes(geometry.attributes),
indices : geometry.indices,
primitiveType : geometry.primitiveType,
boundingSphere : boundingSphere
});
};
/**
* @private
*/
RectangleGeometry.createShadowVolume = function(rectangleGeometry, minHeightFunc, maxHeightFunc) {
var granularity = rectangleGeometry._granularity;
var ellipsoid = rectangleGeometry._ellipsoid;
var minHeight = minHeightFunc(granularity, ellipsoid);
var maxHeight = maxHeightFunc(granularity, ellipsoid);
// TODO: stRotation
return new RectangleGeometry({
rectangle : rectangleGeometry._rectangle,
rotation : rectangleGeometry._rotation,
ellipsoid : ellipsoid,
stRotation : rectangleGeometry._stRotation,
granularity : granularity,
extrudedHeight : maxHeight,
height : minHeight,
closeTop : true,
closeBottom : true,
vertexFormat : VertexFormat.POSITION_ONLY
});
};
defineProperties(RectangleGeometry.prototype, {
/**
* @private
*/
rectangle : {
get : function() {
return this._rotatedRectangle;
}
}
});
return RectangleGeometry;
});
/*global define*/
define('Core/RectangleOutlineGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./Cartographic',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PolygonPipeline',
'./PrimitiveType',
'./Rectangle',
'./RectangleGeometryLibrary'
], function(
BoundingSphere,
Cartesian3,
Cartographic,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PolygonPipeline,
PrimitiveType,
Rectangle,
RectangleGeometryLibrary) {
'use strict';
var bottomBoundingSphere = new BoundingSphere();
var topBoundingSphere = new BoundingSphere();
var positionScratch = new Cartesian3();
var rectangleScratch = new Rectangle();
function constructRectangle(options) {
var size = options.size;
var height = options.height;
var width = options.width;
var positions = new Float64Array(size * 3);
var posIndex = 0;
var row = 0;
var col;
var position = positionScratch;
for (col = 0; col < width; col++) {
RectangleGeometryLibrary.computePosition(options, row, col, position);
positions[posIndex++] = position.x;
positions[posIndex++] = position.y;
positions[posIndex++] = position.z;
}
col = width - 1;
for (row = 1; row < height; row++) {
RectangleGeometryLibrary.computePosition(options, row, col, position);
positions[posIndex++] = position.x;
positions[posIndex++] = position.y;
positions[posIndex++] = position.z;
}
row = height - 1;
for (col = width-2; col >=0; col--){
RectangleGeometryLibrary.computePosition(options, row, col, position);
positions[posIndex++] = position.x;
positions[posIndex++] = position.y;
positions[posIndex++] = position.z;
}
col = 0;
for (row = height - 2; row > 0; row--) {
RectangleGeometryLibrary.computePosition(options, row, col, position);
positions[posIndex++] = position.x;
positions[posIndex++] = position.y;
positions[posIndex++] = position.z;
}
var indicesSize = positions.length/3 * 2;
var indices = IndexDatatype.createTypedArray(positions.length / 3, indicesSize);
var index = 0;
for(var i = 0; i < (positions.length/3)-1; i++) {
indices[index++] = i;
indices[index++] = i+1;
}
indices[index++] = (positions.length/3)-1;
indices[index++] = 0;
var geo = new Geometry({
attributes : new GeometryAttributes(),
primitiveType : PrimitiveType.LINES
});
geo.attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
geo.indices = indices;
return geo;
}
function constructExtrudedRectangle(options) {
var surfaceHeight = options.surfaceHeight;
var extrudedHeight = options.extrudedHeight;
var ellipsoid = options.ellipsoid;
var minHeight = Math.min(extrudedHeight, surfaceHeight);
var maxHeight = Math.max(extrudedHeight, surfaceHeight);
var geo = constructRectangle(options);
if (CesiumMath.equalsEpsilon(minHeight, maxHeight, CesiumMath.EPSILON10)) {
return geo;
}
var height = options.height;
var width = options.width;
var topPositions = PolygonPipeline.scaleToGeodeticHeight(geo.attributes.position.values, maxHeight, ellipsoid, false);
var length = topPositions.length;
var positions = new Float64Array(length*2);
positions.set(topPositions);
var bottomPositions = PolygonPipeline.scaleToGeodeticHeight(geo.attributes.position.values, minHeight, ellipsoid);
positions.set(bottomPositions, length);
geo.attributes.position.values = positions;
var indicesSize = positions.length/3 * 2 + 8;
var indices = IndexDatatype.createTypedArray(positions.length / 3, indicesSize);
length = positions.length/6;
var index = 0;
for (var i = 0; i < length - 1; i++) {
indices[index++] = i;
indices[index++] =i+1;
indices[index++] = i + length;
indices[index++] = i + length + 1;
}
indices[index++] = length - 1;
indices[index++] = 0;
indices[index++] = length + length - 1;
indices[index++] = length;
indices[index++] = 0;
indices[index++] = length;
indices[index++] = width-1;
indices[index++] = length + width-1;
indices[index++] = width + height - 2;
indices[index++] = width + height - 2 + length;
indices[index++] = 2*width + height - 3;
indices[index++] = 2*width + height - 3 + length;
geo.indices = indices;
return geo;
}
/**
* A description of the outline of a a cartographic rectangle on an ellipsoid centered at the origin.
*
* @alias RectangleOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Rectangle} options.rectangle A cartographic rectangle with north, south, east and west properties in radians.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle lies.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Number} [options.height=0.0] The distance in meters between the rectangle and the ellipsoid surface.
* @param {Number} [options.rotation=0.0] The rotation of the rectangle, in radians. A positive rotation is counter-clockwise.
* @param {Number} [options.extrudedHeight] The distance in meters between the rectangle's extruded face and the ellipsoid surface.
*
* @exception {DeveloperError} options.rectangle.north
must be in the interval [-Pi/2
, Pi/2
].
* @exception {DeveloperError} options.rectangle.south
must be in the interval [-Pi/2
, Pi/2
].
* @exception {DeveloperError} options.rectangle.east
must be in the interval [-Pi
, Pi
].
* @exception {DeveloperError} options.rectangle.west
must be in the interval [-Pi
, Pi
].
* @exception {DeveloperError} options.rectangle.north
must be greater than rectangle.south
.
*
* @see RectangleOutlineGeometry#createGeometry
*
* @example
* var rectangle = new Cesium.RectangleOutlineGeometry({
* ellipsoid : Cesium.Ellipsoid.WGS84,
* rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
* height : 10000.0
* });
* var geometry = Cesium.RectangleOutlineGeometry.createGeometry(rectangle);
*/
function RectangleOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var rectangle = options.rectangle;
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
var surfaceHeight = defaultValue(options.height, 0.0);
var rotation = defaultValue(options.rotation, 0.0);
var extrudedHeight = options.extrudedHeight;
if (!defined(rectangle)) {
throw new DeveloperError('rectangle is required.');
}
Rectangle.validate(rectangle);
if (rectangle.north < rectangle.south) {
throw new DeveloperError('options.rectangle.north must be greater than options.rectangle.south');
}
this._rectangle = rectangle;
this._granularity = granularity;
this._ellipsoid = ellipsoid;
this._surfaceHeight = surfaceHeight;
this._rotation = rotation;
this._extrudedHeight = extrudedHeight;
this._workerName = 'createRectangleOutlineGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
RectangleOutlineGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + 5;
/**
* Stores the provided instance into the provided array.
*
* @param {RectangleOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
RectangleOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
Rectangle.pack(value._rectangle, array, startingIndex);
startingIndex += Rectangle.packedLength;
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex++] = value._granularity;
array[startingIndex++] = value._surfaceHeight;
array[startingIndex++] = value._rotation;
array[startingIndex++] = defined(value._extrudedHeight) ? 1.0 : 0.0;
array[startingIndex] = defaultValue(value._extrudedHeight, 0.0);
return array;
};
var scratchRectangle = new Rectangle();
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchOptions = {
rectangle : scratchRectangle,
ellipsoid : scratchEllipsoid,
granularity : undefined,
height : undefined,
rotation : undefined,
extrudedHeight : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {RectangleOutlineGeometry} [result] The object into which to store the result.
* @returns {RectangleOutlineGeometry} The modified result parameter or a new Quaternion instance if one was not provided.
*/
RectangleOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
startingIndex += Rectangle.packedLength;
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var granularity = array[startingIndex++];
var height = array[startingIndex++];
var rotation = array[startingIndex++];
var hasExtrudedHeight = array[startingIndex++];
var extrudedHeight = array[startingIndex];
if (!defined(result)) {
scratchOptions.granularity = granularity;
scratchOptions.height = height;
scratchOptions.rotation = rotation;
scratchOptions.extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
return new RectangleOutlineGeometry(scratchOptions);
}
result._rectangle = Rectangle.clone(rectangle, result._rectangle);
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._surfaceHeight = height;
result._rotation = rotation;
result._extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
return result;
};
var nwScratch = new Cartographic();
/**
* Computes the geometric representation of an outline of an rectangle, including its vertices, indices, and a bounding sphere.
*
* @param {RectangleOutlineGeometry} rectangleGeometry A description of the rectangle outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*
* @exception {DeveloperError} Rotated rectangle is invalid.
*/
RectangleOutlineGeometry.createGeometry = function(rectangleGeometry) {
var rectangle = Rectangle.clone(rectangleGeometry._rectangle, rectangleScratch);
var ellipsoid = rectangleGeometry._ellipsoid;
var surfaceHeight = rectangleGeometry._surfaceHeight;
var extrudedHeight = rectangleGeometry._extrudedHeight;
var options = RectangleGeometryLibrary.computeOptions(rectangleGeometry, rectangle, nwScratch);
options.size = 2*options.width + 2*options.height - 4;
var geometry;
var boundingSphere;
rectangle = rectangleGeometry._rectangle;
if ((CesiumMath.equalsEpsilon(rectangle.north, rectangle.south, CesiumMath.EPSILON10) ||
(CesiumMath.equalsEpsilon(rectangle.east, rectangle.west, CesiumMath.EPSILON10)))) {
return undefined;
}
if (defined(extrudedHeight)) {
geometry = constructExtrudedRectangle(options);
var topBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight, topBoundingSphere);
var bottomBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, extrudedHeight, bottomBoundingSphere);
boundingSphere = BoundingSphere.union(topBS, bottomBS);
} else {
geometry = constructRectangle(options);
geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.attributes.position.values, surfaceHeight, ellipsoid, false);
boundingSphere = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight);
}
return new Geometry({
attributes : geometry.attributes,
indices : geometry.indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : boundingSphere
});
};
return RectangleOutlineGeometry;
});
/*global define*/
define('Core/ReferenceFrame',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Constants for identifying well-known reference frames.
*
* @exports ReferenceFrame
*/
var ReferenceFrame = {
/**
* The fixed frame.
*
* @type {Number}
* @constant
*/
FIXED : 0,
/**
* The inertial frame.
*
* @type {Number}
* @constant
*/
INERTIAL : 1
};
return freezeObject(ReferenceFrame);
});
/*global define*/
define('Core/requestAnimationFrame',[
'./defined',
'./getTimestamp'
], function(
defined,
getTimestamp) {
'use strict';
if (typeof window === 'undefined') {
return;
}
var implementation = window.requestAnimationFrame;
(function() {
// look for vendor prefixed function
if (!defined(implementation)) {
var vendors = ['webkit', 'moz', 'ms', 'o'];
var i = 0;
var len = vendors.length;
while (i < len && !defined(implementation)) {
implementation = window[vendors[i] + 'RequestAnimationFrame'];
++i;
}
}
// build an implementation based on setTimeout
if (!defined(implementation)) {
var msPerFrame = 1000.0 / 60.0;
var lastFrameTime = 0;
implementation = function(callback) {
var currentTime = getTimestamp();
// schedule the callback to target 60fps, 16.7ms per frame,
// accounting for the time taken by the callback
var delay = Math.max(msPerFrame - (currentTime - lastFrameTime), 0);
lastFrameTime = currentTime + delay;
return setTimeout(function() {
callback(lastFrameTime);
}, delay);
};
}
})();
/**
* A browser-independent function to request a new animation frame. This is used to create
* an application's draw loop as shown in the example below.
*
* @exports requestAnimationFrame
*
* @param {requestAnimationFrame~Callback} callback The function to call when the next frame should be drawn.
* @returns {Number} An ID that can be passed to {@link cancelAnimationFrame} to cancel the request.
*
*
* @example
* // Create a draw loop using requestAnimationFrame. The
* // tick callback function is called for every animation frame.
* function tick() {
* scene.render();
* Cesium.requestAnimationFrame(tick);
* }
* tick();
*
* @see {@link http://www.w3.org/TR/animation-timing/#the-WindowAnimationTiming-interface|The WindowAnimationTiming interface}
*/
function requestAnimationFrame(callback) {
// we need this extra wrapper function because the native requestAnimationFrame
// functions must be invoked on the global scope (window), which is not the case
// if invoked as Cesium.requestAnimationFrame(callback)
return implementation(callback);
}
/**
* A function that will be called when the next frame should be drawn.
* @callback requestAnimationFrame~Callback
*
* @param {Number} timestamp A timestamp for the frame, in milliseconds.
*/
return requestAnimationFrame;
});
/*global define*/
define('Core/sampleTerrain',[
'../ThirdParty/when',
'./defined',
'./DeveloperError'
], function(
when,
defined,
DeveloperError) {
'use strict';
/**
* Initiates a terrain height query for an array of {@link Cartographic} positions by
* requesting tiles from a terrain provider, sampling, and interpolating. The interpolation
* matches the triangles used to render the terrain at the specified level. The query
* happens asynchronously, so this function returns a promise that is resolved when
* the query completes. Each point height is modified in place. If a height can not be
* determined because no terrain data is available for the specified level at that location,
* or another error occurs, the height is set to undefined. As is typical of the
* {@link Cartographic} type, the supplied height is a height above the reference ellipsoid
* (such as {@link Ellipsoid.WGS84}) rather than an altitude above mean sea level. In other
* words, it will not necessarily be 0.0 if sampled in the ocean.
*
* @exports sampleTerrain
*
* @param {TerrainProvider} terrainProvider The terrain provider from which to query heights.
* @param {Number} level The terrain level-of-detail from which to query terrain heights.
* @param {Cartographic[]} positions The positions to update with terrain heights.
* @returns {Promise.} A promise that resolves to the provided list of positions when terrain the query has completed.
*
* @example
* // Query the terrain height of two Cartographic positions
* var terrainProvider = new Cesium.CesiumTerrainProvider({
* url : 'https://assets.agi.com/stk-terrain/world'
* });
* var positions = [
* Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
* Cesium.Cartographic.fromDegrees(87.0, 28.0)
* ];
* var promise = Cesium.sampleTerrain(terrainProvider, 11, positions);
* Cesium.when(promise, function(updatedPositions) {
* // positions[0].height and positions[1].height have been updated.
* // updatedPositions is just a reference to positions.
* });
*/
function sampleTerrain(terrainProvider, level, positions) {
if (!defined(terrainProvider)) {
throw new DeveloperError('terrainProvider is required.');
}
if (!defined(level)) {
throw new DeveloperError('level is required.');
}
if (!defined(positions)) {
throw new DeveloperError('positions is required.');
}
var deferred = when.defer();
function doSamplingWhenReady() {
if (terrainProvider.ready) {
when(doSampling(terrainProvider, level, positions), function(updatedPositions) {
deferred.resolve(updatedPositions);
});
} else {
setTimeout(doSamplingWhenReady, 10);
}
}
doSamplingWhenReady();
return deferred.promise;
}
function doSampling(terrainProvider, level, positions) {
var tilingScheme = terrainProvider.tilingScheme;
var i;
// Sort points into a set of tiles
var tileRequests = []; // Result will be an Array as it's easier to work with
var tileRequestSet = {}; // A unique set
for (i = 0; i < positions.length; ++i) {
var xy = tilingScheme.positionToTileXY(positions[i], level);
var key = xy.toString();
if (!tileRequestSet.hasOwnProperty(key)) {
// When tile is requested for the first time
var value = {
x : xy.x,
y : xy.y,
level : level,
tilingScheme : tilingScheme,
terrainProvider : terrainProvider,
positions : []
};
tileRequestSet[key] = value;
tileRequests.push(value);
}
// Now append to array of points for the tile
tileRequestSet[key].positions.push(positions[i]);
}
// Send request for each required tile
var tilePromises = [];
for (i = 0; i < tileRequests.length; ++i) {
var tileRequest = tileRequests[i];
var requestPromise = tileRequest.terrainProvider.requestTileGeometry(tileRequest.x, tileRequest.y, tileRequest.level, false);
var tilePromise = when(requestPromise, createInterpolateFunction(tileRequest), createMarkFailedFunction(tileRequest));
tilePromises.push(tilePromise);
}
return when.all(tilePromises, function() {
return positions;
});
}
function createInterpolateFunction(tileRequest) {
var tilePositions = tileRequest.positions;
var rectangle = tileRequest.tilingScheme.tileXYToRectangle(tileRequest.x, tileRequest.y, tileRequest.level);
return function(terrainData) {
for (var i = 0; i < tilePositions.length; ++i) {
var position = tilePositions[i];
position.height = terrainData.interpolateHeight(rectangle, position.longitude, position.latitude);
}
};
}
function createMarkFailedFunction(tileRequest) {
var tilePositions = tileRequest.positions;
return function() {
for (var i = 0; i < tilePositions.length; ++i) {
var position = tilePositions[i];
position.height = undefined;
}
};
}
return sampleTerrain;
});
/*global define*/
define('Core/ScreenSpaceEventType',[
'./freezeObject'
], function(
freezeObject) {
'use strict';
/**
* This enumerated type is for classifying mouse events: down, up, click, double click, move and move while a button is held down.
*
* @exports ScreenSpaceEventType
*/
var ScreenSpaceEventType = {
/**
* Represents a mouse left button down event.
*
* @type {Number}
* @constant
*/
LEFT_DOWN : 0,
/**
* Represents a mouse left button up event.
*
* @type {Number}
* @constant
*/
LEFT_UP : 1,
/**
* Represents a mouse left click event.
*
* @type {Number}
* @constant
*/
LEFT_CLICK : 2,
/**
* Represents a mouse left double click event.
*
* @type {Number}
* @constant
*/
LEFT_DOUBLE_CLICK : 3,
/**
* Represents a mouse left button down event.
*
* @type {Number}
* @constant
*/
RIGHT_DOWN : 5,
/**
* Represents a mouse right button up event.
*
* @type {Number}
* @constant
*/
RIGHT_UP : 6,
/**
* Represents a mouse right click event.
*
* @type {Number}
* @constant
*/
RIGHT_CLICK : 7,
/**
* Represents a mouse right double click event.
*
* @type {Number}
* @constant
*/
RIGHT_DOUBLE_CLICK : 8,
/**
* Represents a mouse middle button down event.
*
* @type {Number}
* @constant
*/
MIDDLE_DOWN : 10,
/**
* Represents a mouse middle button up event.
*
* @type {Number}
* @constant
*/
MIDDLE_UP : 11,
/**
* Represents a mouse middle click event.
*
* @type {Number}
* @constant
*/
MIDDLE_CLICK : 12,
/**
* Represents a mouse middle double click event.
*
* @type {Number}
* @constant
*/
MIDDLE_DOUBLE_CLICK : 13,
/**
* Represents a mouse move event.
*
* @type {Number}
* @constant
*/
MOUSE_MOVE : 15,
/**
* Represents a mouse wheel event.
*
* @type {Number}
* @constant
*/
WHEEL : 16,
/**
* Represents the start of a two-finger event on a touch surface.
*
* @type {Number}
* @constant
*/
PINCH_START : 17,
/**
* Represents the end of a two-finger event on a touch surface.
*
* @type {Number}
* @constant
*/
PINCH_END : 18,
/**
* Represents a change of a two-finger event on a touch surface.
*
* @type {Number}
* @constant
*/
PINCH_MOVE : 19
};
return freezeObject(ScreenSpaceEventType);
});
/*global define*/
define('Core/ScreenSpaceEventHandler',[
'./AssociativeArray',
'./Cartesian2',
'./defaultValue',
'./defined',
'./destroyObject',
'./DeveloperError',
'./FeatureDetection',
'./getTimestamp',
'./KeyboardEventModifier',
'./ScreenSpaceEventType'
], function(
AssociativeArray,
Cartesian2,
defaultValue,
defined,
destroyObject,
DeveloperError,
FeatureDetection,
getTimestamp,
KeyboardEventModifier,
ScreenSpaceEventType) {
'use strict';
function getPosition(screenSpaceEventHandler, event, result) {
var element = screenSpaceEventHandler._element;
if (element === document) {
result.x = event.clientX;
result.y = event.clientY;
return result;
}
var rect = element.getBoundingClientRect();
result.x = event.clientX - rect.left;
result.y = event.clientY - rect.top;
return result;
}
function getInputEventKey(type, modifier) {
var key = type;
if (defined(modifier)) {
key += '+' + modifier;
}
return key;
}
function getModifier(event) {
if (event.shiftKey) {
return KeyboardEventModifier.SHIFT;
} else if (event.ctrlKey) {
return KeyboardEventModifier.CTRL;
} else if (event.altKey) {
return KeyboardEventModifier.ALT;
}
return undefined;
}
var MouseButton = {
LEFT : 0,
MIDDLE : 1,
RIGHT : 2
};
function registerListener(screenSpaceEventHandler, domType, element, callback) {
function listener(e) {
callback(screenSpaceEventHandler, e);
}
element.addEventListener(domType, listener, false);
screenSpaceEventHandler._removalFunctions.push(function() {
element.removeEventListener(domType, listener, false);
});
}
function registerListeners(screenSpaceEventHandler) {
var element = screenSpaceEventHandler._element;
// some listeners may be registered on the document, so we still get events even after
// leaving the bounds of element.
// this is affected by the existence of an undocumented disableRootEvents property on element.
var alternateElement = !defined(element.disableRootEvents) ? document : element;
if (FeatureDetection.supportsPointerEvents()) {
registerListener(screenSpaceEventHandler, 'pointerdown', element, handlePointerDown);
registerListener(screenSpaceEventHandler, 'pointerup', element, handlePointerUp);
registerListener(screenSpaceEventHandler, 'pointermove', element, handlePointerMove);
registerListener(screenSpaceEventHandler, 'pointercancel', element, handlePointerUp);
} else {
registerListener(screenSpaceEventHandler, 'mousedown', element, handleMouseDown);
registerListener(screenSpaceEventHandler, 'mouseup', alternateElement, handleMouseUp);
registerListener(screenSpaceEventHandler, 'mousemove', alternateElement, handleMouseMove);
registerListener(screenSpaceEventHandler, 'touchstart', element, handleTouchStart);
registerListener(screenSpaceEventHandler, 'touchend', alternateElement, handleTouchEnd);
registerListener(screenSpaceEventHandler, 'touchmove', alternateElement, handleTouchMove);
registerListener(screenSpaceEventHandler, 'touchcancel', alternateElement, handleTouchEnd);
}
registerListener(screenSpaceEventHandler, 'dblclick', element, handleDblClick);
// detect available wheel event
var wheelEvent;
if ('onwheel' in element) {
// spec event type
wheelEvent = 'wheel';
} else if (document.onmousewheel !== undefined) {
// legacy event type
wheelEvent = 'mousewheel';
} else {
// older Firefox
wheelEvent = 'DOMMouseScroll';
}
registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel);
}
function unregisterListeners(screenSpaceEventHandler) {
var removalFunctions = screenSpaceEventHandler._removalFunctions;
for (var i = 0; i < removalFunctions.length; ++i) {
removalFunctions[i]();
}
}
var mouseDownEvent = {
position : new Cartesian2()
};
function gotTouchEvent(screenSpaceEventHandler) {
screenSpaceEventHandler._lastSeenTouchEvent = getTimestamp();
}
function canProcessMouseEvent(screenSpaceEventHandler) {
return (getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent) > ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
}
function handleMouseDown(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
}
var button = event.button;
screenSpaceEventHandler._buttonDown = button;
var screenSpaceEventType;
if (button === MouseButton.LEFT) {
screenSpaceEventType = ScreenSpaceEventType.LEFT_DOWN;
} else if (button === MouseButton.MIDDLE) {
screenSpaceEventType = ScreenSpaceEventType.MIDDLE_DOWN;
} else if (button === MouseButton.RIGHT) {
screenSpaceEventType = ScreenSpaceEventType.RIGHT_DOWN;
} else {
return;
}
var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition);
Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);
var modifier = getModifier(event);
var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier);
if (defined(action)) {
Cartesian2.clone(position, mouseDownEvent.position);
action(mouseDownEvent);
event.preventDefault();
}
}
var mouseUpEvent = {
position : new Cartesian2()
};
var mouseClickEvent = {
position : new Cartesian2()
};
function handleMouseUp(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
}
var button = event.button;
screenSpaceEventHandler._buttonDown = undefined;
var screenSpaceEventType;
var clickScreenSpaceEventType;
if (button === MouseButton.LEFT) {
screenSpaceEventType = ScreenSpaceEventType.LEFT_UP;
clickScreenSpaceEventType = ScreenSpaceEventType.LEFT_CLICK;
} else if (button === MouseButton.MIDDLE) {
screenSpaceEventType = ScreenSpaceEventType.MIDDLE_UP;
clickScreenSpaceEventType = ScreenSpaceEventType.MIDDLE_CLICK;
} else if (button === MouseButton.RIGHT) {
screenSpaceEventType = ScreenSpaceEventType.RIGHT_UP;
clickScreenSpaceEventType = ScreenSpaceEventType.RIGHT_CLICK;
} else {
return;
}
var modifier = getModifier(event);
var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier);
var clickAction = screenSpaceEventHandler.getInputAction(clickScreenSpaceEventType, modifier);
if (defined(action) || defined(clickAction)) {
var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition);
if (defined(action)) {
Cartesian2.clone(position, mouseUpEvent.position);
action(mouseUpEvent);
}
if (defined(clickAction)) {
var startPosition = screenSpaceEventHandler._primaryStartPosition;
var xDiff = startPosition.x - position.x;
var yDiff = startPosition.y - position.y;
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
Cartesian2.clone(position, mouseClickEvent.position);
clickAction(mouseClickEvent);
}
}
}
}
var mouseMoveEvent = {
startPosition : new Cartesian2(),
endPosition : new Cartesian2()
};
function handleMouseMove(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
}
var modifier = getModifier(event);
var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition);
var previousPosition = screenSpaceEventHandler._primaryPreviousPosition;
var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier);
if (defined(action)) {
Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition);
Cartesian2.clone(position, mouseMoveEvent.endPosition);
action(mouseMoveEvent);
}
Cartesian2.clone(position, previousPosition);
if (defined(screenSpaceEventHandler._buttonDown)) {
event.preventDefault();
}
}
var mouseDblClickEvent = {
position : new Cartesian2()
};
function handleDblClick(screenSpaceEventHandler, event) {
var button = event.button;
var screenSpaceEventType;
if (button === MouseButton.LEFT) {
screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK;
} else if (button === MouseButton.MIDDLE) {
screenSpaceEventType = ScreenSpaceEventType.MIDDLE_DOUBLE_CLICK;
} else if (button === MouseButton.RIGHT) {
screenSpaceEventType = ScreenSpaceEventType.RIGHT_DOUBLE_CLICK;
} else {
return;
}
var modifier = getModifier(event);
var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier);
if (defined(action)) {
getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position);
action(mouseDblClickEvent);
}
}
function handleWheel(screenSpaceEventHandler, event) {
// currently this event exposes the delta value in terms of
// the obsolete mousewheel event type. so, for now, we adapt the other
// values to that scheme.
var delta;
// standard wheel event uses deltaY. sign is opposite wheelDelta.
// deltaMode indicates what unit it is in.
if (defined(event.deltaY)) {
var deltaMode = event.deltaMode;
if (deltaMode === event.DOM_DELTA_PIXEL) {
delta = -event.deltaY;
} else if (deltaMode === event.DOM_DELTA_LINE) {
delta = -event.deltaY * 40;
} else {
// DOM_DELTA_PAGE
delta = -event.deltaY * 120;
}
} else if (event.detail > 0) {
// old Firefox versions use event.detail to count the number of clicks. The sign
// of the integer is the direction the wheel is scrolled.
delta = event.detail * -120;
} else {
delta = event.wheelDelta;
}
if (!defined(delta)) {
return;
}
var modifier = getModifier(event);
var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.WHEEL, modifier);
if (defined(action)) {
action(delta);
event.preventDefault();
}
}
function handleTouchStart(screenSpaceEventHandler, event) {
gotTouchEvent(screenSpaceEventHandler);
var changedTouches = event.changedTouches;
var i;
var length = changedTouches.length;
var touch;
var identifier;
var positions = screenSpaceEventHandler._positions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
positions.set(identifier, getPosition(screenSpaceEventHandler, touch, new Cartesian2()));
}
fireTouchEvents(screenSpaceEventHandler, event);
var previousPositions = screenSpaceEventHandler._previousPositions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
previousPositions.set(identifier, Cartesian2.clone(positions.get(identifier)));
}
}
function handleTouchEnd(screenSpaceEventHandler, event) {
gotTouchEvent(screenSpaceEventHandler);
var changedTouches = event.changedTouches;
var i;
var length = changedTouches.length;
var touch;
var identifier;
var positions = screenSpaceEventHandler._positions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
positions.remove(identifier);
}
fireTouchEvents(screenSpaceEventHandler, event);
var previousPositions = screenSpaceEventHandler._previousPositions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
previousPositions.remove(identifier);
}
}
var touchStartEvent = {
position : new Cartesian2()
};
var touch2StartEvent = {
position1 : new Cartesian2(),
position2 : new Cartesian2()
};
var touchEndEvent = {
position : new Cartesian2()
};
var touchClickEvent = {
position : new Cartesian2()
};
function fireTouchEvents(screenSpaceEventHandler, event) {
var modifier = getModifier(event);
var positions = screenSpaceEventHandler._positions;
var previousPositions = screenSpaceEventHandler._previousPositions;
var numberOfTouches = positions.length;
var action;
var clickAction;
if (numberOfTouches !== 1 && screenSpaceEventHandler._buttonDown === MouseButton.LEFT) {
// transitioning from single touch, trigger UP and might trigger CLICK
screenSpaceEventHandler._buttonDown = undefined;
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_UP, modifier);
if (defined(action)) {
Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchEndEvent.position);
action(touchEndEvent);
}
if (numberOfTouches === 0) {
// releasing single touch, check for CLICK
clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_CLICK, modifier);
if (defined(clickAction)) {
var startPosition = screenSpaceEventHandler._primaryStartPosition;
var endPosition = previousPositions.values[0];
var xDiff = startPosition.x - endPosition.x;
var yDiff = startPosition.y - endPosition.y;
var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchClickEvent.position);
clickAction(touchClickEvent);
}
}
}
// Otherwise don't trigger CLICK, because we are adding more touches.
}
if (numberOfTouches !== 2 && screenSpaceEventHandler._isPinching) {
// transitioning from pinch, trigger PINCH_END
screenSpaceEventHandler._isPinching = false;
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.PINCH_END, modifier);
if (defined(action)) {
action();
}
}
if (numberOfTouches === 1) {
// transitioning to single touch, trigger DOWN
var position = positions.values[0];
Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);
screenSpaceEventHandler._buttonDown = MouseButton.LEFT;
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_DOWN, modifier);
if (defined(action)) {
Cartesian2.clone(position, touchStartEvent.position);
action(touchStartEvent);
}
event.preventDefault();
}
if (numberOfTouches === 2) {
// transitioning to pinch, trigger PINCH_START
screenSpaceEventHandler._isPinching = true;
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.PINCH_START, modifier);
if (defined(action)) {
Cartesian2.clone(positions.values[0], touch2StartEvent.position1);
Cartesian2.clone(positions.values[1], touch2StartEvent.position2);
action(touch2StartEvent);
// Touch-enabled devices, in particular iOS can have many default behaviours for
// "pinch" events, which can still be executed unless we prevent them here.
event.preventDefault();
}
}
}
function handleTouchMove(screenSpaceEventHandler, event) {
gotTouchEvent(screenSpaceEventHandler);
var changedTouches = event.changedTouches;
var i;
var length = changedTouches.length;
var touch;
var identifier;
var positions = screenSpaceEventHandler._positions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
var position = positions.get(identifier);
if (defined(position)) {
getPosition(screenSpaceEventHandler, touch, position);
}
}
fireTouchMoveEvents(screenSpaceEventHandler, event);
var previousPositions = screenSpaceEventHandler._previousPositions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
Cartesian2.clone(positions.get(identifier), previousPositions.get(identifier));
}
}
var touchMoveEvent = {
startPosition : new Cartesian2(),
endPosition : new Cartesian2()
};
var touchPinchMovementEvent = {
distance : {
startPosition : new Cartesian2(),
endPosition : new Cartesian2()
},
angleAndHeight : {
startPosition : new Cartesian2(),
endPosition : new Cartesian2()
}
};
function fireTouchMoveEvents(screenSpaceEventHandler, event) {
var modifier = getModifier(event);
var positions = screenSpaceEventHandler._positions;
var previousPositions = screenSpaceEventHandler._previousPositions;
var numberOfTouches = positions.length;
var action;
if (numberOfTouches === 1 && screenSpaceEventHandler._buttonDown === MouseButton.LEFT) {
// moving single touch
var position = positions.values[0];
Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
var previousPosition = screenSpaceEventHandler._primaryPreviousPosition;
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier);
if (defined(action)) {
Cartesian2.clone(previousPosition, touchMoveEvent.startPosition);
Cartesian2.clone(position, touchMoveEvent.endPosition);
action(touchMoveEvent);
}
Cartesian2.clone(position, previousPosition);
event.preventDefault();
} else if (numberOfTouches === 2 && screenSpaceEventHandler._isPinching) {
// moving pinch
action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.PINCH_MOVE, modifier);
if (defined(action)) {
var position1 = positions.values[0];
var position2 = positions.values[1];
var previousPosition1 = previousPositions.values[0];
var previousPosition2 = previousPositions.values[1];
var dX = position2.x - position1.x;
var dY = position2.y - position1.y;
var dist = Math.sqrt(dX * dX + dY * dY) * 0.25;
var prevDX = previousPosition2.x - previousPosition1.x;
var prevDY = previousPosition2.y - previousPosition1.y;
var prevDist = Math.sqrt(prevDX * prevDX + prevDY * prevDY) * 0.25;
var cY = (position2.y + position1.y) * 0.125;
var prevCY = (previousPosition2.y + previousPosition1.y) * 0.125;
var angle = Math.atan2(dY, dX);
var prevAngle = Math.atan2(prevDY, prevDX);
Cartesian2.fromElements(0.0, prevDist, touchPinchMovementEvent.distance.startPosition);
Cartesian2.fromElements(0.0, dist, touchPinchMovementEvent.distance.endPosition);
Cartesian2.fromElements(prevAngle, prevCY, touchPinchMovementEvent.angleAndHeight.startPosition);
Cartesian2.fromElements(angle, cY, touchPinchMovementEvent.angleAndHeight.endPosition);
action(touchPinchMovementEvent);
}
}
}
function handlePointerDown(screenSpaceEventHandler, event) {
event.target.setPointerCapture(event.pointerId);
if (event.pointerType === 'touch') {
var positions = screenSpaceEventHandler._positions;
var identifier = event.pointerId;
positions.set(identifier, getPosition(screenSpaceEventHandler, event, new Cartesian2()));
fireTouchEvents(screenSpaceEventHandler, event);
var previousPositions = screenSpaceEventHandler._previousPositions;
previousPositions.set(identifier, Cartesian2.clone(positions.get(identifier)));
} else {
handleMouseDown(screenSpaceEventHandler, event);
}
}
function handlePointerUp(screenSpaceEventHandler, event) {
if (event.pointerType === 'touch') {
var positions = screenSpaceEventHandler._positions;
var identifier = event.pointerId;
positions.remove(identifier);
fireTouchEvents(screenSpaceEventHandler, event);
var previousPositions = screenSpaceEventHandler._previousPositions;
previousPositions.remove(identifier);
} else {
handleMouseUp(screenSpaceEventHandler, event);
}
}
function handlePointerMove(screenSpaceEventHandler, event) {
if (event.pointerType === 'touch') {
var positions = screenSpaceEventHandler._positions;
var identifier = event.pointerId;
var position = positions.get(identifier);
if(!defined(position)){
return;
}
getPosition(screenSpaceEventHandler, event, position);
fireTouchMoveEvents(screenSpaceEventHandler, event);
var previousPositions = screenSpaceEventHandler._previousPositions;
Cartesian2.clone(positions.get(identifier), previousPositions.get(identifier));
} else {
handleMouseMove(screenSpaceEventHandler, event);
}
}
/**
* Handles user input events. Custom functions can be added to be executed on
* when the user enters input.
*
* @alias ScreenSpaceEventHandler
*
* @param {Canvas} [element=document] The element to add events to.
*
* @constructor
*/
function ScreenSpaceEventHandler(element) {
this._inputEvents = {};
this._buttonDown = undefined;
this._isPinching = false;
this._lastSeenTouchEvent = -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
this._primaryStartPosition = new Cartesian2();
this._primaryPosition = new Cartesian2();
this._primaryPreviousPosition = new Cartesian2();
this._positions = new AssociativeArray();
this._previousPositions = new AssociativeArray();
this._removalFunctions = [];
// TODO: Revisit when doing mobile development. May need to be configurable
// or determined based on the platform?
this._clickPixelTolerance = 5;
this._element = defaultValue(element, document);
registerListeners(this);
}
/**
* Set a function to be executed on an input event.
*
* @param {Function} action Function to be executed when the input event occurs.
* @param {Number} type The ScreenSpaceEventType of input event.
* @param {Number} [modifier] A KeyboardEventModifier key that is held when a type
* event occurs.
*
* @see ScreenSpaceEventHandler#getInputAction
* @see ScreenSpaceEventHandler#removeInputAction
*/
ScreenSpaceEventHandler.prototype.setInputAction = function(action, type, modifier) {
if (!defined(action)) {
throw new DeveloperError('action is required.');
}
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getInputEventKey(type, modifier);
this._inputEvents[key] = action;
};
/**
* Returns the function to be executed on an input event.
*
* @param {Number} type The ScreenSpaceEventType of input event.
* @param {Number} [modifier] A KeyboardEventModifier key that is held when a type
* event occurs.
*
* @see ScreenSpaceEventHandler#setInputAction
* @see ScreenSpaceEventHandler#removeInputAction
*/
ScreenSpaceEventHandler.prototype.getInputAction = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getInputEventKey(type, modifier);
return this._inputEvents[key];
};
/**
* Removes the function to be executed on an input event.
*
* @param {Number} type The ScreenSpaceEventType of input event.
* @param {Number} [modifier] A KeyboardEventModifier key that is held when a type
* event occurs.
*
* @see ScreenSpaceEventHandler#getInputAction
* @see ScreenSpaceEventHandler#setInputAction
*/
ScreenSpaceEventHandler.prototype.removeInputAction = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getInputEventKey(type, modifier);
delete this._inputEvents[key];
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see ScreenSpaceEventHandler#destroy
*/
ScreenSpaceEventHandler.prototype.isDestroyed = function() {
return false;
};
/**
* Removes listeners held by this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* handler = handler && handler.destroy();
*
* @see ScreenSpaceEventHandler#isDestroyed
*/
ScreenSpaceEventHandler.prototype.destroy = function() {
unregisterListeners(this);
return destroyObject(this);
};
/**
* The amount of time, in milliseconds, that mouse events will be disabled after
* receiving any touch events, such that any emulated mouse events will be ignored.
* @default 800
*/
ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;
return ScreenSpaceEventHandler;
});
/*global define*/
define('Core/ShowGeometryInstanceAttribute',[
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError'
], function(
ComponentDatatype,
defaultValue,
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* Value and type information for per-instance geometry attribute that determines if the geometry instance will be shown.
*
* @alias ShowGeometryInstanceAttribute
* @constructor
*
* @param {Boolean} [show=true] Determines if the geometry instance will be shown.
*
*
* @example
* var instance = new Cesium.GeometryInstance({
* geometry : new Cesium.BoxGeometry({
* vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL,
* minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0),
* maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0)
* }),
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
* id : 'box',
* attributes : {
* show : new Cesium.ShowGeometryInstanceAttribute(false)
* }
* });
*
* @see GeometryInstance
* @see GeometryInstanceAttribute
*/
function ShowGeometryInstanceAttribute(show) {
show = defaultValue(show, true);
/**
* The values for the attributes stored in a typed array.
*
* @type Uint8Array
*
* @default [1.0]
*/
this.value = ShowGeometryInstanceAttribute.toValue(show);
}
defineProperties(ShowGeometryInstanceAttribute.prototype, {
/**
* The datatype of each component in the attribute, e.g., individual elements in
* {@link ColorGeometryInstanceAttribute#value}.
*
* @memberof ShowGeometryInstanceAttribute.prototype
*
* @type {ComponentDatatype}
* @readonly
*
* @default {@link ComponentDatatype.UNSIGNED_BYTE}
*/
componentDatatype : {
get : function() {
return ComponentDatatype.UNSIGNED_BYTE;
}
},
/**
* The number of components in the attributes, i.e., {@link ColorGeometryInstanceAttribute#value}.
*
* @memberof ShowGeometryInstanceAttribute.prototype
*
* @type {Number}
* @readonly
*
* @default 1
*/
componentsPerAttribute : {
get : function() {
return 1;
}
},
/**
* When true
and componentDatatype
is an integer format,
* indicate that the components should be mapped to the range [0, 1] (unsigned)
* or [-1, 1] (signed) when they are accessed as floating-point for rendering.
*
* @memberof ShowGeometryInstanceAttribute.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
normalize : {
get : function() {
return false;
}
}
});
/**
* Converts a boolean show to a typed array that can be used to assign a show attribute.
*
* @param {Boolean} show The show value.
* @param {Uint8Array} [result] The array to store the result in, if undefined a new instance will be created.
* @returns {Uint8Array} The modified result parameter or a new instance if result was undefined.
*
* @example
* var attributes = primitive.getGeometryInstanceAttributes('an id');
* attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true, attributes.show);
*/
ShowGeometryInstanceAttribute.toValue = function(show, result) {
if (!defined(show)) {
throw new DeveloperError('show is required.');
}
if (!defined(result)) {
return new Uint8Array([show]);
}
result[0] = show;
return result;
};
return ShowGeometryInstanceAttribute;
});
/*global define*/
define('Core/Simon1994PlanetaryPositions',[
'./Cartesian3',
'./defined',
'./DeveloperError',
'./JulianDate',
'./Math',
'./Matrix3',
'./TimeConstants',
'./TimeStandard'
], function(
Cartesian3,
defined,
DeveloperError,
JulianDate,
CesiumMath,
Matrix3,
TimeConstants,
TimeStandard) {
'use strict';
/**
* Contains functions for finding the Cartesian coordinates of the sun and the moon in the
* Earth-centered inertial frame.
*
* @exports Simon1994PlanetaryPositions
*/
var Simon1994PlanetaryPositions = {};
function computeTdbMinusTtSpice(daysSinceJ2000InTerrestrialTime) {
/* STK Comments ------------------------------------------------------
* This function uses constants designed to be consistent with
* the SPICE Toolkit from JPL version N0051 (unitim.c)
* M0 = 6.239996
* M0Dot = 1.99096871e-7 rad/s = 0.01720197 rad/d
* EARTH_ECC = 1.671e-2
* TDB_AMPL = 1.657e-3 secs
*--------------------------------------------------------------------*/
//* Values taken as specified in STK Comments except: 0.01720197 rad/day = 1.99096871e-7 rad/sec
//* Here we use the more precise value taken from the SPICE value 1.99096871e-7 rad/sec converted to rad/day
//* All other constants are consistent with the SPICE implementation of the TDB conversion
//* except where we treat the independent time parameter to be in TT instead of TDB.
//* This is an approximation made to facilitate performance due to the higher prevalance of
//* the TT2TDB conversion over TDB2TT in order to avoid having to iterate when converting to TDB for the JPL ephemeris.
//* Days are used instead of seconds to provide a slight improvement in numerical precision.
//* For more information see:
//* http://www.cv.nrao.edu/~rfisher/Ephemerides/times.html#TDB
//* ftp://ssd.jpl.nasa.gov/pub/eph/planets/ioms/ExplSupplChap8.pdf
var g = 6.239996 + (0.0172019696544) * daysSinceJ2000InTerrestrialTime;
return 1.657e-3 * Math.sin(g + 1.671e-2 * Math.sin(g));
}
var TdtMinusTai = 32.184;
var J2000d = 2451545;
function taiToTdb(date, result) {
//Converts TAI to TT
result = JulianDate.addSeconds(date, TdtMinusTai, result);
//Converts TT to TDB
var days = JulianDate.totalDays(result) - J2000d;
result = JulianDate.addSeconds(result, computeTdbMinusTtSpice(days), result);
return result;
}
var epoch = new JulianDate(2451545, 0, TimeStandard.TAI); //Actually TDB (not TAI)
var MetersPerKilometer = 1000.0;
var RadiansPerDegree = CesiumMath.RADIANS_PER_DEGREE;
var RadiansPerArcSecond = CesiumMath.RADIANS_PER_ARCSECOND;
var MetersPerAstronomicalUnit = 1.49597870e+11; // IAU 1976 value
var perifocalToEquatorial = new Matrix3();
function elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, longitudeOfNode, meanLongitude, result) {
if (inclination < 0.0) {
inclination = -inclination;
longitudeOfNode += CesiumMath.PI;
}
if (inclination < 0 || inclination > CesiumMath.PI) {
throw new DeveloperError('The inclination is out of range. Inclination must be greater than or equal to zero and less than or equal to Pi radians.');
}
var radiusOfPeriapsis = semimajorAxis * (1.0 - eccentricity);
var argumentOfPeriapsis = longitudeOfPerigee - longitudeOfNode;
var rightAscensionOfAscendingNode = longitudeOfNode;
var trueAnomaly = meanAnomalyToTrueAnomaly(meanLongitude - longitudeOfPerigee, eccentricity);
var type = chooseOrbit(eccentricity, 0.0);
if (type === 'Hyperbolic' && Math.abs(CesiumMath.negativePiToPi(trueAnomaly)) >= Math.acos(- 1.0 / eccentricity)) {
throw new DeveloperError('The true anomaly of the hyperbolic orbit lies outside of the bounds of the hyperbola.');
}
perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscensionOfAscendingNode, perifocalToEquatorial);
var semilatus = radiusOfPeriapsis * (1.0 + eccentricity);
var costheta = Math.cos(trueAnomaly);
var sintheta = Math.sin(trueAnomaly);
var denom = (1.0 + eccentricity * costheta);
if (denom <= CesiumMath.Epsilon10) {
throw new DeveloperError('elements cannot be converted to cartesian');
}
var radius = semilatus / denom;
if (!defined(result)) {
result = new Cartesian3(radius * costheta, radius * sintheta, 0.0);
} else {
result.x = radius * costheta;
result.y = radius * sintheta;
result.z = 0.0;
}
return Matrix3.multiplyByVector(perifocalToEquatorial, result, result);
}
function chooseOrbit(eccentricity, tolerance) {
if (eccentricity < 0) {
throw new DeveloperError('eccentricity cannot be negative.');
}
if (eccentricity <= tolerance) {
return 'Circular';
} else if (eccentricity < 1.0 - tolerance) {
return 'Elliptical';
} else if (eccentricity <= 1.0 + tolerance) {
return 'Parabolic';
} else {
return 'Hyperbolic';
}
}
// Calculates the true anomaly given the mean anomaly and the eccentricity.
function meanAnomalyToTrueAnomaly(meanAnomaly, eccentricity) {
if (eccentricity < 0.0 || eccentricity >= 1.0) {
throw new DeveloperError('eccentricity out of range.');
}
var eccentricAnomaly = meanAnomalyToEccentricAnomaly(meanAnomaly, eccentricity);
return eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity);
}
var maxIterationCount = 50;
var keplerEqConvergence = CesiumMath.EPSILON8;
// Calculates the eccentric anomaly given the mean anomaly and the eccentricity.
function meanAnomalyToEccentricAnomaly(meanAnomaly, eccentricity) {
if (eccentricity < 0.0 || eccentricity >= 1.0) {
throw new DeveloperError('eccentricity out of range.');
}
var revs = Math.floor(meanAnomaly / CesiumMath.TWO_PI);
// Find angle in current revolution
meanAnomaly -= revs * CesiumMath.TWO_PI;
// calculate starting value for iteration sequence
var iterationValue = meanAnomaly + (eccentricity * Math.sin(meanAnomaly)) /
(1.0 - Math.sin(meanAnomaly + eccentricity) + Math.sin(meanAnomaly));
// Perform Newton-Raphson iteration on Kepler's equation
var eccentricAnomaly = Number.MAX_VALUE;
var count;
for (count = 0;
count < maxIterationCount && Math.abs(eccentricAnomaly - iterationValue) > keplerEqConvergence;
++count)
{
eccentricAnomaly = iterationValue;
var NRfunction = eccentricAnomaly - eccentricity * Math.sin(eccentricAnomaly) - meanAnomaly;
var dNRfunction = 1 - eccentricity * Math.cos(eccentricAnomaly);
iterationValue = eccentricAnomaly - NRfunction / dNRfunction;
}
if (count >= maxIterationCount) {
throw new DeveloperError('Kepler equation did not converge');
// STK Components uses a numerical method to find the eccentric anomaly in the case that Kepler's
// equation does not converge. We don't expect that to ever be necessary for the reasonable orbits used here.
}
eccentricAnomaly = iterationValue + revs * CesiumMath.TWO_PI;
return eccentricAnomaly;
}
// Calculates the true anomaly given the eccentric anomaly and the eccentricity.
function eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity) {
if (eccentricity < 0.0 || eccentricity >= 1.0) {
throw new DeveloperError('eccentricity out of range.');
}
// Calculate the number of previous revolutions
var revs = Math.floor(eccentricAnomaly / CesiumMath.TWO_PI);
// Find angle in current revolution
eccentricAnomaly -= revs * CesiumMath.TWO_PI;
// Calculate true anomaly from eccentric anomaly
var trueAnomalyX = Math.cos(eccentricAnomaly) - eccentricity;
var trueAnomalyY = Math.sin(eccentricAnomaly) * Math.sqrt(1 - eccentricity * eccentricity);
var trueAnomaly = Math.atan2(trueAnomalyY, trueAnomalyX);
// Ensure the correct quadrant
trueAnomaly = CesiumMath.zeroToTwoPi(trueAnomaly);
if (eccentricAnomaly < 0)
{
trueAnomaly -= CesiumMath.TWO_PI;
}
// Add on previous revolutions
trueAnomaly += revs * CesiumMath.TWO_PI;
return trueAnomaly;
}
// Calculates the transformation matrix to convert from the perifocal (PQW) coordinate
// system to inertial cartesian coordinates.
function perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscension, result) {
if (inclination < 0 || inclination > CesiumMath.PI) {
throw new DeveloperError('inclination out of range');
}
var cosap = Math.cos(argumentOfPeriapsis);
var sinap = Math.sin(argumentOfPeriapsis);
var cosi = Math.cos(inclination);
var sini = Math.sin(inclination);
var cosraan = Math.cos(rightAscension);
var sinraan = Math.sin(rightAscension);
if (!defined(result)) {
result = new Matrix3(
cosraan * cosap - sinraan * sinap * cosi,
-cosraan * sinap - sinraan * cosap * cosi,
sinraan * sini,
sinraan * cosap + cosraan * sinap * cosi,
-sinraan * sinap + cosraan * cosap * cosi,
-cosraan * sini,
sinap * sini,
cosap * sini,
cosi);
} else {
result[0] = cosraan * cosap - sinraan * sinap * cosi;
result[1] = sinraan * cosap + cosraan * sinap * cosi;
result[2] = sinap * sini;
result[3] = -cosraan * sinap - sinraan * cosap * cosi;
result[4] = -sinraan * sinap + cosraan * cosap * cosi;
result[5] = cosap * sini;
result[6] = sinraan * sini;
result[7] = -cosraan * sini;
result[8] = cosi;
}
return result;
}
// From section 5.8
var semiMajorAxis0 = 1.0000010178 * MetersPerAstronomicalUnit;
var meanLongitude0 = 100.46645683 * RadiansPerDegree;
var meanLongitude1 = 1295977422.83429 * RadiansPerArcSecond;
// From table 6
var p1u = 16002;
var p2u = 21863;
var p3u = 32004;
var p4u = 10931;
var p5u = 14529;
var p6u = 16368;
var p7u = 15318;
var p8u = 32794;
var Ca1 = 64 * 1e-7 * MetersPerAstronomicalUnit;
var Ca2 = -152 * 1e-7 * MetersPerAstronomicalUnit;
var Ca3 = 62 * 1e-7 * MetersPerAstronomicalUnit;
var Ca4 = -8 * 1e-7 * MetersPerAstronomicalUnit;
var Ca5 = 32 * 1e-7 * MetersPerAstronomicalUnit;
var Ca6 = -41 * 1e-7 * MetersPerAstronomicalUnit;
var Ca7 = 19 * 1e-7 * MetersPerAstronomicalUnit;
var Ca8 = -11 * 1e-7 * MetersPerAstronomicalUnit;
var Sa1 = -150 * 1e-7 * MetersPerAstronomicalUnit;
var Sa2 = -46 * 1e-7 * MetersPerAstronomicalUnit;
var Sa3 = 68 * 1e-7 * MetersPerAstronomicalUnit;
var Sa4 = 54 * 1e-7 * MetersPerAstronomicalUnit;
var Sa5 = 14 * 1e-7 * MetersPerAstronomicalUnit;
var Sa6 = 24 * 1e-7 * MetersPerAstronomicalUnit;
var Sa7 = -28 * 1e-7 * MetersPerAstronomicalUnit;
var Sa8 = 22 * 1e-7 * MetersPerAstronomicalUnit;
var q1u = 10;
var q2u = 16002;
var q3u = 21863;
var q4u = 10931;
var q5u = 1473;
var q6u = 32004;
var q7u = 4387;
var q8u = 73;
var Cl1 = -325 * 1e-7;
var Cl2 = -322 * 1e-7;
var Cl3 = -79 * 1e-7;
var Cl4 = 232 * 1e-7;
var Cl5 = -52 * 1e-7;
var Cl6 = 97 * 1e-7;
var Cl7 = 55 * 1e-7;
var Cl8 = -41 * 1e-7;
var Sl1 = -105 * 1e-7;
var Sl2 = -137 * 1e-7;
var Sl3 = 258 * 1e-7;
var Sl4 = 35 * 1e-7;
var Sl5 = -116 * 1e-7;
var Sl6 = -88 * 1e-7;
var Sl7 = -112 * 1e-7;
var Sl8 = -80 * 1e-7;
var scratchDate = new JulianDate(0, 0.0, TimeStandard.TAI);
/**
* Gets a point describing the motion of the Earth-Moon barycenter according to the equations
* described in section 6.
*/
function computeSimonEarthMoonBarycenter(date, result) {
// t is thousands of years from J2000 TDB
taiToTdb(date, scratchDate);
var x = (scratchDate.dayNumber - epoch.dayNumber) + ((scratchDate.secondsOfDay - epoch.secondsOfDay)/TimeConstants.SECONDS_PER_DAY);
var t = x / (TimeConstants.DAYS_PER_JULIAN_CENTURY * 10.0);
var u = 0.35953620 * t;
var semimajorAxis = semiMajorAxis0 +
Ca1 * Math.cos(p1u * u) + Sa1 * Math.sin(p1u * u) +
Ca2 * Math.cos(p2u * u) + Sa2 * Math.sin(p2u * u) +
Ca3 * Math.cos(p3u * u) + Sa3 * Math.sin(p3u * u) +
Ca4 * Math.cos(p4u * u) + Sa4 * Math.sin(p4u * u) +
Ca5 * Math.cos(p5u * u) + Sa5 * Math.sin(p5u * u) +
Ca6 * Math.cos(p6u * u) + Sa6 * Math.sin(p6u * u) +
Ca7 * Math.cos(p7u * u) + Sa7 * Math.sin(p7u * u) +
Ca8 * Math.cos(p8u * u) + Sa8 * Math.sin(p8u * u);
var meanLongitude = meanLongitude0 + meanLongitude1 * t +
Cl1 * Math.cos(q1u * u) + Sl1 * Math.sin(q1u * u) +
Cl2 * Math.cos(q2u * u) + Sl2 * Math.sin(q2u * u) +
Cl3 * Math.cos(q3u * u) + Sl3 * Math.sin(q3u * u) +
Cl4 * Math.cos(q4u * u) + Sl4 * Math.sin(q4u * u) +
Cl5 * Math.cos(q5u * u) + Sl5 * Math.sin(q5u * u) +
Cl6 * Math.cos(q6u * u) + Sl6 * Math.sin(q6u * u) +
Cl7 * Math.cos(q7u * u) + Sl7 * Math.sin(q7u * u) +
Cl8 * Math.cos(q8u * u) + Sl8 * Math.sin(q8u * u);
// All constants in this part are from section 5.8
var eccentricity = 0.0167086342 - 0.0004203654 * t;
var longitudeOfPerigee = 102.93734808 * RadiansPerDegree + 11612.35290 * RadiansPerArcSecond * t;
var inclination = 469.97289 * RadiansPerArcSecond * t;
var longitudeOfNode = 174.87317577 * RadiansPerDegree - 8679.27034 * RadiansPerArcSecond * t;
return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee,
longitudeOfNode, meanLongitude, result);
}
/**
* Gets a point describing the position of the moon according to the equations described in section 4.
*/
function computeSimonMoon(date, result) {
taiToTdb(date, scratchDate);
var x = (scratchDate.dayNumber - epoch.dayNumber) + ((scratchDate.secondsOfDay - epoch.secondsOfDay)/TimeConstants.SECONDS_PER_DAY);
var t = x / (TimeConstants.DAYS_PER_JULIAN_CENTURY);
var t2 = t * t;
var t3 = t2 * t;
var t4 = t3 * t;
// Terms from section 3.4 (b.1)
var semimajorAxis = 383397.7725 + 0.0040 * t;
var eccentricity = 0.055545526 - 0.000000016 * t;
var inclinationConstant = 5.15668983 * RadiansPerDegree;
var inclinationSecPart = -0.00008 * t + 0.02966 * t2 -
0.000042 * t3 - 0.00000013 * t4;
var longitudeOfPerigeeConstant = 83.35324312 * RadiansPerDegree;
var longitudeOfPerigeeSecPart = 14643420.2669 * t - 38.2702 * t2 -
0.045047 * t3 + 0.00021301 * t4;
var longitudeOfNodeConstant = 125.04455501 * RadiansPerDegree;
var longitudeOfNodeSecPart = -6967919.3631 * t + 6.3602 * t2 +
0.007625 * t3 - 0.00003586 * t4;
var meanLongitudeConstant = 218.31664563 * RadiansPerDegree;
var meanLongitudeSecPart = 1732559343.48470 * t - 6.3910 * t2 +
0.006588 * t3 - 0.00003169 * t4;
// Delaunay arguments from section 3.5 b
var D = 297.85019547 * RadiansPerDegree + RadiansPerArcSecond *
(1602961601.2090 * t - 6.3706 * t2 + 0.006593 * t3 - 0.00003169 * t4);
var F = 93.27209062 * RadiansPerDegree + RadiansPerArcSecond *
(1739527262.8478 * t - 12.7512 * t2 - 0.001037 * t3 + 0.00000417 * t4);
var l = 134.96340251 * RadiansPerDegree + RadiansPerArcSecond *
(1717915923.2178 * t + 31.8792 * t2 + 0.051635 * t3 - 0.00024470 * t4);
var lprime = 357.52910918 * RadiansPerDegree + RadiansPerArcSecond *
(129596581.0481 * t - 0.5532 * t2 + 0.000136 * t3 - 0.00001149 * t4);
var psi = 310.17137918 * RadiansPerDegree - RadiansPerArcSecond *
(6967051.4360 * t + 6.2068 * t2 + 0.007618 * t3 - 0.00003219 * t4);
// Add terms from Table 4
var twoD = 2.0 * D;
var fourD = 4.0 * D;
var sixD = 6.0 * D;
var twol = 2.0 * l;
var threel = 3.0 * l;
var fourl = 4.0 * l;
var twoF = 2.0 * F;
semimajorAxis += 3400.4 * Math.cos(twoD) - 635.6 * Math.cos(twoD - l) -
235.6 * Math.cos(l) + 218.1 * Math.cos(twoD - lprime) +
181.0 * Math.cos(twoD + l);
eccentricity += 0.014216 * Math.cos(twoD - l) + 0.008551 * Math.cos(twoD - twol) -
0.001383 * Math.cos(l) + 0.001356 * Math.cos(twoD + l) -
0.001147 * Math.cos(fourD - threel) - 0.000914 * Math.cos(fourD - twol) +
0.000869 * Math.cos(twoD - lprime - l) - 0.000627 * Math.cos(twoD) -
0.000394 * Math.cos(fourD - fourl) + 0.000282 * Math.cos(twoD - lprime - twol) -
0.000279 * Math.cos(D - l) - 0.000236 * Math.cos(twol) +
0.000231 * Math.cos(fourD) + 0.000229 * Math.cos(sixD - fourl) -
0.000201 * Math.cos(twol - twoF);
inclinationSecPart += 486.26 * Math.cos(twoD - twoF) - 40.13 * Math.cos(twoD) +
37.51 * Math.cos(twoF) + 25.73 * Math.cos(twol - twoF) +
19.97 * Math.cos(twoD - lprime - twoF);
longitudeOfPerigeeSecPart += -55609 * Math.sin(twoD - l) - 34711 * Math.sin(twoD - twol) -
9792 * Math.sin(l) + 9385 * Math.sin(fourD - threel) +
7505 * Math.sin(fourD - twol) + 5318 * Math.sin(twoD + l) +
3484 * Math.sin(fourD - fourl) - 3417 * Math.sin(twoD - lprime - l) -
2530 * Math.sin(sixD - fourl) - 2376 * Math.sin(twoD) -
2075 * Math.sin(twoD - threel) - 1883 * Math.sin(twol) -
1736 * Math.sin(sixD - 5.0 * l) + 1626 * Math.sin(lprime) -
1370 * Math.sin(sixD - threel);
longitudeOfNodeSecPart += -5392 * Math.sin(twoD - twoF) - 540 * Math.sin(lprime) -
441 * Math.sin(twoD) + 423 * Math.sin(twoF) -
288 * Math.sin(twol - twoF);
meanLongitudeSecPart += -3332.9 * Math.sin(twoD) + 1197.4 * Math.sin(twoD - l) -
662.5 * Math.sin(lprime) + 396.3 * Math.sin(l) -
218.0 * Math.sin(twoD - lprime);
// Add terms from Table 5
var twoPsi = 2.0 * psi;
var threePsi = 3.0 * psi;
inclinationSecPart += 46.997 * Math.cos(psi) * t - 0.614 * Math.cos(twoD - twoF + psi) * t +
0.614 * Math.cos(twoD - twoF - psi) * t - 0.0297 * Math.cos(twoPsi) * t2 -
0.0335 * Math.cos(psi) * t2 + 0.0012 * Math.cos(twoD - twoF + twoPsi) * t2 -
0.00016 * Math.cos(psi) * t3 + 0.00004 * Math.cos(threePsi) * t3 +
0.00004 * Math.cos(twoPsi) * t3;
var perigeeAndMean = 2.116 * Math.sin(psi) * t - 0.111 * Math.sin(twoD - twoF - psi) * t -
0.0015 * Math.sin(psi) * t2;
longitudeOfPerigeeSecPart += perigeeAndMean;
meanLongitudeSecPart += perigeeAndMean;
longitudeOfNodeSecPart += -520.77 * Math.sin(psi) * t + 13.66 * Math.sin(twoD - twoF + psi) * t +
1.12 * Math.sin(twoD - psi) * t - 1.06 * Math.sin(twoF - psi) * t +
0.660 * Math.sin(twoPsi) * t2 + 0.371 * Math.sin(psi) * t2 -
0.035 * Math.sin(twoD - twoF + twoPsi) * t2 - 0.015 * Math.sin(twoD - twoF + psi) * t2 +
0.0014 * Math.sin(psi) * t3 - 0.0011 * Math.sin(threePsi) * t3 -
0.0009 * Math.sin(twoPsi) * t3;
// Add constants and convert units
semimajorAxis *= MetersPerKilometer;
var inclination = inclinationConstant + inclinationSecPart * RadiansPerArcSecond;
var longitudeOfPerigee = longitudeOfPerigeeConstant + longitudeOfPerigeeSecPart * RadiansPerArcSecond;
var meanLongitude = meanLongitudeConstant + meanLongitudeSecPart * RadiansPerArcSecond;
var longitudeOfNode = longitudeOfNodeConstant + longitudeOfNodeSecPart * RadiansPerArcSecond;
return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee,
longitudeOfNode, meanLongitude, result);
}
/**
* Gets a point describing the motion of the Earth. This point uses the Moon point and
* the 1992 mu value (ratio between Moon and Earth masses) in Table 2 of the paper in order
* to determine the position of the Earth relative to the Earth-Moon barycenter.
*/
var moonEarthMassRatio = 0.012300034; // From 1992 mu value in Table 2
var factor = moonEarthMassRatio / (moonEarthMassRatio + 1.0) * -1;
function computeSimonEarth(date, result) {
result = computeSimonMoon(date, result);
return Cartesian3.multiplyByScalar(result, factor, result);
}
// Values for the axesTransformation
needed for the rotation were found using the STK Components
// GreographicTransformer on the position of the sun center of mass point and the earth J2000 frame.
var axesTransformation = new Matrix3(1.0000000000000002, 5.619723173785822e-16, 4.690511510146299e-19,
-5.154129427414611e-16, 0.9174820620691819, -0.39777715593191376,
-2.23970096136568e-16, 0.39777715593191376, 0.9174820620691819);
var translation = new Cartesian3();
/**
* Computes the position of the Sun in the Earth-centered inertial frame
*
* @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} Calculated sun position
*/
Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame= function(date, result){
if (!defined(date)) {
date = JulianDate.now();
}
if (!defined(result)) {
result = new Cartesian3();
}
//first forward transformation
translation = computeSimonEarthMoonBarycenter(date, translation);
result = Cartesian3.negate(translation, result);
//second forward transformation
computeSimonEarth(date, translation);
Cartesian3.subtract(result, translation, result);
Matrix3.multiplyByVector(axesTransformation, result, result);
return result;
};
/**
* Computes the position of the Moon in the Earth-centered inertial frame
*
* @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3} Calculated moon position
*/
Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame = function(date, result){
if (!defined(date)) {
date = JulianDate.now();
}
result = computeSimonMoon(date, result);
Matrix3.multiplyByVector(axesTransformation, result, result);
return result;
};
return Simon1994PlanetaryPositions;
});
/*global define*/
define('Core/SimplePolylineGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./Color',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PolylinePipeline',
'./PrimitiveType'
], function(
BoundingSphere,
Cartesian3,
Color,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PolylinePipeline,
PrimitiveType) {
'use strict';
function interpolateColors(p0, p1, color0, color1, minDistance, array, offset) {
var numPoints = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
var i;
var r0 = color0.red;
var g0 = color0.green;
var b0 = color0.blue;
var a0 = color0.alpha;
var r1 = color1.red;
var g1 = color1.green;
var b1 = color1.blue;
var a1 = color1.alpha;
if (Color.equals(color0, color1)) {
for (i = 0; i < numPoints; i++) {
array[offset++] = Color.floatToByte(r0);
array[offset++] = Color.floatToByte(g0);
array[offset++] = Color.floatToByte(b0);
array[offset++] = Color.floatToByte(a0);
}
return offset;
}
var redPerVertex = (r1 - r0) / numPoints;
var greenPerVertex = (g1 - g0) / numPoints;
var bluePerVertex = (b1 - b0) / numPoints;
var alphaPerVertex = (a1 - a0) / numPoints;
var index = offset;
for (i = 0; i < numPoints; i++) {
array[index++] = Color.floatToByte(r0 + i * redPerVertex);
array[index++] = Color.floatToByte(g0 + i * greenPerVertex);
array[index++] = Color.floatToByte(b0 + i * bluePerVertex);
array[index++] = Color.floatToByte(a0 + i * alphaPerVertex);
}
return index;
}
/**
* A description of a polyline modeled as a line strip; the first two positions define a line segment,
* and each additional position defines a line segment from the previous position.
*
* @alias SimplePolylineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the positions in the polyline as a line strip.
* @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors.
* @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices.
* @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
*
* @exception {DeveloperError} At least two positions are required.
* @exception {DeveloperError} colors has an invalid length.
*
* @see SimplePolylineGeometry#createGeometry
*
* @example
* // A polyline with two connected line segments
* var polyline = new Cesium.SimplePolylineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 0.0, 0.0,
* 5.0, 0.0,
* 5.0, 5.0
* ])
* });
* var geometry = Cesium.SimplePolylineGeometry.createGeometry(polyline);
*/
function SimplePolylineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.positions;
var colors = options.colors;
var colorsPerVertex = defaultValue(options.colorsPerVertex, false);
if ((!defined(positions)) || (positions.length < 2)) {
throw new DeveloperError('At least two positions are required.');
}
if (defined(colors) && ((colorsPerVertex && colors.length < positions.length) || (!colorsPerVertex && colors.length < positions.length - 1))) {
throw new DeveloperError('colors has an invalid length.');
}
this._positions = positions;
this._colors = colors;
this._colorsPerVertex = colorsPerVertex;
this._followSurface = defaultValue(options.followSurface, true);
this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
this._workerName = 'createSimplePolylineGeometry';
var numComponents = 1 + positions.length * Cartesian3.packedLength;
numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1;
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = numComponents + Ellipsoid.packedLength + 3;
}
/**
* Stores the provided instance into the provided array.
*
* @param {SimplePolylineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
SimplePolylineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
var colors = value._colors;
length = defined(colors) ? colors.length : 0.0;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
Color.pack(colors[i], array, startingIndex);
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0;
array[startingIndex++] = value._followSurface ? 1.0 : 0.0;
array[startingIndex] = value._granularity;
return array;
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {SimplePolylineGeometry} [result] The object into which to store the result.
* @returns {SimplePolylineGeometry} The modified result parameter or a new SimplePolylineGeometry instance if one was not provided.
*/
SimplePolylineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var length = array[startingIndex++];
var positions = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
length = array[startingIndex++];
var colors = length > 0 ? new Array(length) : undefined;
for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
colors[i] = Color.unpack(array, startingIndex);
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex);
startingIndex += Ellipsoid.packedLength;
var colorsPerVertex = array[startingIndex++] === 1.0;
var followSurface = array[startingIndex++] === 1.0;
var granularity = array[startingIndex];
if (!defined(result)) {
return new SimplePolylineGeometry({
positions : positions,
colors : colors,
ellipsoid : ellipsoid,
colorsPerVertex : colorsPerVertex,
followSurface : followSurface,
granularity : granularity
});
}
result._positions = positions;
result._colors = colors;
result._ellipsoid = ellipsoid;
result._colorsPerVertex = colorsPerVertex;
result._followSurface = followSurface;
result._granularity = granularity;
return result;
};
var scratchArray1 = new Array(2);
var scratchArray2 = new Array(2);
var generateArcOptionsScratch = {
positions : scratchArray1,
height: scratchArray2,
ellipsoid: undefined,
minDistance : undefined
};
/**
* Computes the geometric representation of a simple polyline, including its vertices, indices, and a bounding sphere.
*
* @param {SimplePolylineGeometry} simplePolylineGeometry A description of the polyline.
* @returns {Geometry} The computed vertices and indices.
*/
SimplePolylineGeometry.createGeometry = function(simplePolylineGeometry) {
var positions = simplePolylineGeometry._positions;
var colors = simplePolylineGeometry._colors;
var colorsPerVertex = simplePolylineGeometry._colorsPerVertex;
var followSurface = simplePolylineGeometry._followSurface;
var granularity = simplePolylineGeometry._granularity;
var ellipsoid = simplePolylineGeometry._ellipsoid;
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
var perSegmentColors = defined(colors) && !colorsPerVertex;
var i;
var length = positions.length;
var positionValues;
var numberOfPositions;
var colorValues;
var color;
var offset = 0;
if (followSurface) {
var heights = PolylinePipeline.extractHeights(positions, ellipsoid);
var generateArcOptions = generateArcOptionsScratch;
generateArcOptions.minDistance = minDistance;
generateArcOptions.ellipsoid = ellipsoid;
if (perSegmentColors) {
var positionCount = 0;
for (i = 0; i < length - 1; i++) {
positionCount += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance) + 1;
}
positionValues = new Float64Array(positionCount * 3);
colorValues = new Uint8Array(positionCount * 4);
generateArcOptions.positions = scratchArray1;
generateArcOptions.height= scratchArray2;
var ci = 0;
for (i = 0; i < length - 1; ++i) {
scratchArray1[0] = positions[i];
scratchArray1[1] = positions[i + 1];
scratchArray2[0] = heights[i];
scratchArray2[1] = heights[i + 1];
var pos = PolylinePipeline.generateArc(generateArcOptions);
if (defined(colors)) {
var segLen = pos.length / 3;
color = colors[i];
for(var k = 0; k < segLen; ++k) {
colorValues[ci++] = Color.floatToByte(color.red);
colorValues[ci++] = Color.floatToByte(color.green);
colorValues[ci++] = Color.floatToByte(color.blue);
colorValues[ci++] = Color.floatToByte(color.alpha);
}
}
positionValues.set(pos, offset);
offset += pos.length;
}
} else {
generateArcOptions.positions = positions;
generateArcOptions.height= heights;
positionValues = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));
if (defined(colors)) {
colorValues = new Uint8Array(positionValues.length / 3 * 4);
for (i = 0; i < length - 1; ++i) {
var p0 = positions[i];
var p1 = positions[i + 1];
var c0 = colors[i];
var c1 = colors[i + 1];
offset = interpolateColors(p0, p1, c0, c1, minDistance, colorValues, offset);
}
var lastColor = colors[length - 1];
colorValues[offset++] = Color.floatToByte(lastColor.red);
colorValues[offset++] = Color.floatToByte(lastColor.green);
colorValues[offset++] = Color.floatToByte(lastColor.blue);
colorValues[offset++] = Color.floatToByte(lastColor.alpha);
}
}
} else {
numberOfPositions = perSegmentColors ? length * 2 - 2 : length;
positionValues = new Float64Array(numberOfPositions * 3);
colorValues = defined(colors) ? new Uint8Array(numberOfPositions * 4) : undefined;
var positionIndex = 0;
var colorIndex = 0;
for (i = 0; i < length; ++i) {
var p = positions[i];
if (perSegmentColors && i > 0) {
Cartesian3.pack(p, positionValues, positionIndex);
positionIndex += 3;
color = colors[i - 1];
colorValues[colorIndex++] = Color.floatToByte(color.red);
colorValues[colorIndex++] = Color.floatToByte(color.green);
colorValues[colorIndex++] = Color.floatToByte(color.blue);
colorValues[colorIndex++] = Color.floatToByte(color.alpha);
}
if (perSegmentColors && i === length - 1) {
break;
}
Cartesian3.pack(p, positionValues, positionIndex);
positionIndex += 3;
if (defined(colors)) {
color = colors[i];
colorValues[colorIndex++] = Color.floatToByte(color.red);
colorValues[colorIndex++] = Color.floatToByte(color.green);
colorValues[colorIndex++] = Color.floatToByte(color.blue);
colorValues[colorIndex++] = Color.floatToByte(color.alpha);
}
}
}
var attributes = new GeometryAttributes();
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positionValues
});
if (defined(colors)) {
attributes.color = new GeometryAttribute({
componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
componentsPerAttribute : 4,
values : colorValues,
normalize : true
});
}
numberOfPositions = positionValues.length / 3;
var numberOfIndices = (numberOfPositions - 1) * 2;
var indices = IndexDatatype.createTypedArray(numberOfPositions, numberOfIndices);
var index = 0;
for (i = 0; i < numberOfPositions - 1; ++i) {
indices[index++] = i;
indices[index++] = i + 1;
}
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : BoundingSphere.fromPoints(positions)
});
};
return SimplePolylineGeometry;
});
/*global define*/
define('Core/SphereGeometry',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./EllipsoidGeometry',
'./VertexFormat'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
EllipsoidGeometry,
VertexFormat) {
'use strict';
/**
* A description of a sphere centered at the origin.
*
* @alias SphereGeometry
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.radius=1.0] The radius of the sphere.
* @param {Number} [options.stackPartitions=64] The number of times to partition the ellipsoid into stacks.
* @param {Number} [options.slicePartitions=64] The number of times to partition the ellipsoid into radial slices.
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
*
* @exception {DeveloperError} options.slicePartitions cannot be less than three.
* @exception {DeveloperError} options.stackPartitions cannot be less than three.
*
* @see SphereGeometry#createGeometry
*
* @example
* var sphere = new Cesium.SphereGeometry({
* radius : 100.0,
* vertexFormat : Cesium.VertexFormat.POSITION_ONLY
* });
* var geometry = Cesium.SphereGeometry.createGeometry(sphere);
*/
function SphereGeometry(options) {
var radius = defaultValue(options.radius, 1.0);
var radii = new Cartesian3(radius, radius, radius);
var ellipsoidOptions = {
radii: radii,
stackPartitions: options.stackPartitions,
slicePartitions: options.slicePartitions,
vertexFormat: options.vertexFormat
};
this._ellipsoidGeometry = new EllipsoidGeometry(ellipsoidOptions);
this._workerName = 'createSphereGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
SphereGeometry.packedLength = EllipsoidGeometry.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {SphereGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
SphereGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
return EllipsoidGeometry.pack(value._ellipsoidGeometry, array, startingIndex);
};
var scratchEllipsoidGeometry = new EllipsoidGeometry();
var scratchOptions = {
radius : undefined,
radii : new Cartesian3(),
vertexFormat : new VertexFormat(),
stackPartitions : undefined,
slicePartitions : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {SphereGeometry} [result] The object into which to store the result.
* @returns {SphereGeometry} The modified result parameter or a new SphereGeometry instance if one was not provided.
*/
SphereGeometry.unpack = function(array, startingIndex, result) {
var ellipsoidGeometry = EllipsoidGeometry.unpack(array, startingIndex, scratchEllipsoidGeometry);
scratchOptions.vertexFormat = VertexFormat.clone(ellipsoidGeometry._vertexFormat, scratchOptions.vertexFormat);
scratchOptions.stackPartitions = ellipsoidGeometry._stackPartitions;
scratchOptions.slicePartitions = ellipsoidGeometry._slicePartitions;
if (!defined(result)) {
scratchOptions.radius = ellipsoidGeometry._radii.x;
return new SphereGeometry(scratchOptions);
}
Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii);
result._ellipsoidGeometry = new EllipsoidGeometry(scratchOptions);
return result;
};
/**
* Computes the geometric representation of a sphere, including its vertices, indices, and a bounding sphere.
*
* @param {SphereGeometry} sphereGeometry A description of the sphere.
* @returns {Geometry} The computed vertices and indices.
*/
SphereGeometry.createGeometry = function(sphereGeometry) {
return EllipsoidGeometry.createGeometry(sphereGeometry._ellipsoidGeometry);
};
return SphereGeometry;
});
/*global define*/
define('Core/SphereOutlineGeometry',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./EllipsoidOutlineGeometry'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
EllipsoidOutlineGeometry) {
'use strict';
/**
* A description of the outline of a sphere.
*
* @alias SphereOutlineGeometry
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.radius=1.0] The radius of the sphere.
* @param {Number} [options.stackPartitions=10] The count of stacks for the sphere (1 greater than the number of parallel lines).
* @param {Number} [options.slicePartitions=8] The count of slices for the sphere (Equal to the number of radial lines).
* @param {Number} [options.subdivisions=200] The number of points per line, determining the granularity of the curvature .
*
* @exception {DeveloperError} options.stackPartitions must be greater than or equal to one.
* @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero.
* @exception {DeveloperError} options.subdivisions must be greater than or equal to zero.
*
* @example
* var sphere = new Cesium.SphereOutlineGeometry({
* radius : 100.0,
* stackPartitions : 6,
* slicePartitions: 5
* });
* var geometry = Cesium.SphereOutlineGeometry.createGeometry(sphere);
*/
function SphereOutlineGeometry(options) {
var radius = defaultValue(options.radius, 1.0);
var radii = new Cartesian3(radius, radius, radius);
var ellipsoidOptions = {
radii: radii,
stackPartitions: options.stackPartitions,
slicePartitions: options.slicePartitions,
subdivisions: options.subdivisions
};
this._ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions);
this._workerName = 'createSphereOutlineGeometry';
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
SphereOutlineGeometry.packedLength = EllipsoidOutlineGeometry.packedLength;
/**
* Stores the provided instance into the provided array.
*
* @param {SphereOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
SphereOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
return EllipsoidOutlineGeometry.pack(value._ellipsoidGeometry, array, startingIndex);
};
var scratchEllipsoidGeometry = new EllipsoidOutlineGeometry();
var scratchOptions = {
radius : undefined,
radii : new Cartesian3(),
stackPartitions : undefined,
slicePartitions : undefined,
subdivisions : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {SphereOutlineGeometry} [result] The object into which to store the result.
* @returns {SphereOutlineGeometry} The modified result parameter or a new SphereOutlineGeometry instance if one was not provided.
*/
SphereOutlineGeometry.unpack = function(array, startingIndex, result) {
var ellipsoidGeometry = EllipsoidOutlineGeometry.unpack(array, startingIndex, scratchEllipsoidGeometry);
scratchOptions.stackPartitions = ellipsoidGeometry._stackPartitions;
scratchOptions.slicePartitions = ellipsoidGeometry._slicePartitions;
scratchOptions.subdivisions = ellipsoidGeometry._subdivisions;
if (!defined(result)) {
scratchOptions.radius = ellipsoidGeometry._radii.x;
return new SphereOutlineGeometry(scratchOptions);
}
Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii);
result._ellipsoidGeometry = new EllipsoidOutlineGeometry(scratchOptions);
return result;
};
/**
* Computes the geometric representation of an outline of a sphere, including its vertices, indices, and a bounding sphere.
*
* @param {SphereOutlineGeometry} sphereGeometry A description of the sphere outline.
* @returns {Geometry} The computed vertices and indices.
*/
SphereOutlineGeometry.createGeometry = function(sphereGeometry) {
return EllipsoidOutlineGeometry.createGeometry(sphereGeometry._ellipsoidGeometry);
};
return SphereOutlineGeometry;
});
/*global define*/
define('Core/Spherical',[
'./defaultValue',
'./defined',
'./DeveloperError'
], function(
defaultValue,
defined,
DeveloperError) {
'use strict';
/**
* A set of curvilinear 3-dimensional coordinates.
*
* @alias Spherical
* @constructor
*
* @param {Number} [clock=0.0] The angular coordinate lying in the xy-plane measured from the positive x-axis and toward the positive y-axis.
* @param {Number} [cone=0.0] The angular coordinate measured from the positive z-axis and toward the negative z-axis.
* @param {Number} [magnitude=1.0] The linear coordinate measured from the origin.
*/
function Spherical(clock, cone, magnitude) {
this.clock = defaultValue(clock, 0.0);
this.cone = defaultValue(cone, 0.0);
this.magnitude = defaultValue(magnitude, 1.0);
}
/**
* Converts the provided Cartesian3 into Spherical coordinates.
*
* @param {Cartesian3} cartesian3 The Cartesian3 to be converted to Spherical.
* @param {Spherical} [spherical] The object in which the result will be stored, if undefined a new instance will be created.
* @returns {Spherical} The modified result parameter, or a new instance if one was not provided.
*/
Spherical.fromCartesian3 = function(cartesian3, result) {
if (!defined(cartesian3)) {
throw new DeveloperError('cartesian3 is required');
}
var x = cartesian3.x;
var y = cartesian3.y;
var z = cartesian3.z;
var radialSquared = x * x + y * y;
if (!defined(result)) {
result = new Spherical();
}
result.clock = Math.atan2(y, x);
result.cone = Math.atan2(Math.sqrt(radialSquared), z);
result.magnitude = Math.sqrt(radialSquared + z * z);
return result;
};
/**
* Creates a duplicate of a Spherical.
*
* @param {Spherical} spherical The spherical to clone.
* @param {Spherical} [result] The object to store the result into, if undefined a new instance will be created.
* @returns {Spherical} The modified result parameter or a new instance if result was undefined. (Returns undefined if spherical is undefined)
*/
Spherical.clone = function(spherical, result) {
if (!defined(spherical)) {
return undefined;
}
if (!defined(result)) {
return new Spherical(spherical.clock, spherical.cone, spherical.magnitude);
}
result.clock = spherical.clock;
result.cone = spherical.cone;
result.magnitude = spherical.magnitude;
return result;
};
/**
* Computes the normalized version of the provided spherical.
*
* @param {Spherical} spherical The spherical to be normalized.
* @param {Spherical} [result] The object to store the result into, if undefined a new instance will be created.
* @returns {Spherical} The modified result parameter or a new instance if result was undefined.
*/
Spherical.normalize = function(spherical, result) {
if (!defined(spherical)) {
throw new DeveloperError('spherical is required');
}
if (!defined(result)) {
return new Spherical(spherical.clock, spherical.cone, 1.0);
}
result.clock = spherical.clock;
result.cone = spherical.cone;
result.magnitude = 1.0;
return result;
};
/**
* Returns true if the first spherical is equal to the second spherical, false otherwise.
*
* @param {Spherical} left The first Spherical to be compared.
* @param {Spherical} right The second Spherical to be compared.
* @returns {Boolean} true if the first spherical is equal to the second spherical, false otherwise.
*/
Spherical.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(left.clock === right.clock) &&
(left.cone === right.cone) &&
(left.magnitude === right.magnitude));
};
/**
* Returns true if the first spherical is within the provided epsilon of the second spherical, false otherwise.
*
* @param {Spherical} left The first Spherical to be compared.
* @param {Spherical} right The second Spherical to be compared.
* @param {Number} [epsilon=0.0] The epsilon to compare against.
* @returns {Boolean} true if the first spherical is within the provided epsilon of the second spherical, false otherwise.
*/
Spherical.equalsEpsilon = function(left, right, epsilon) {
epsilon = defaultValue(epsilon, 0.0);
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
(Math.abs(left.clock - right.clock) <= epsilon) &&
(Math.abs(left.cone - right.cone) <= epsilon) &&
(Math.abs(left.magnitude - right.magnitude) <= epsilon));
};
/**
* Returns true if this spherical is equal to the provided spherical, false otherwise.
*
* @param {Spherical} other The Spherical to be compared.
* @returns {Boolean} true if this spherical is equal to the provided spherical, false otherwise.
*/
Spherical.prototype.equals = function(other) {
return Spherical.equals(this, other);
};
/**
* Creates a duplicate of this Spherical.
*
* @param {Spherical} [result] The object to store the result into, if undefined a new instance will be created.
* @returns {Spherical} The modified result parameter or a new instance if result was undefined.
*/
Spherical.prototype.clone = function(result) {
return Spherical.clone(this, result);
};
/**
* Returns true if this spherical is within the provided epsilon of the provided spherical, false otherwise.
*
* @param {Spherical} other The Spherical to be compared.
* @param {Number} epsilon The epsilon to compare against.
* @returns {Boolean} true if this spherical is within the provided epsilon of the provided spherical, false otherwise.
*/
Spherical.prototype.equalsEpsilon = function(other, epsilon) {
return Spherical.equalsEpsilon(this, other, epsilon);
};
/**
* Returns a string representing this instance in the format (clock, cone, magnitude).
*
* @returns {String} A string representing this instance.
*/
Spherical.prototype.toString = function() {
return '(' + this.clock + ', ' + this.cone + ', ' + this.magnitude + ')';
};
return Spherical;
});
/*global define*/
define('Core/subdivideArray',[
'./defined',
'./DeveloperError'
], function(
defined,
DeveloperError) {
'use strict';
/**
* Subdivides an array into a number of smaller, equal sized arrays.
*
* @exports subdivideArray
*
* @param {Array} array The array to divide.
* @param {Number} numberOfArrays The number of arrays to divide the provided array into.
*
* @exception {DeveloperError} numberOfArrays must be greater than 0.
*/
function subdivideArray(array, numberOfArrays) {
if (!defined(array)) {
throw new DeveloperError('array is required.');
}
if (!defined(numberOfArrays) || numberOfArrays < 1) {
throw new DeveloperError('numberOfArrays must be greater than 0.');
}
var result = [];
var len = array.length;
var i = 0;
while (i < len) {
var size = Math.ceil((len - i) / numberOfArrays--);
result.push(array.slice(i, i + size));
i += size;
}
return result;
}
return subdivideArray;
});
/*global define*/
define('Core/TerrainData',[
'./defineProperties',
'./DeveloperError'
], function(
defineProperties,
DeveloperError) {
'use strict';
/**
* Terrain data for a single tile. This type describes an
* interface and is not intended to be instantiated directly.
*
* @alias TerrainData
* @constructor
*
* @see HeightmapTerrainData
* @see QuantizedMeshTerrainData
*/
function TerrainData() {
DeveloperError.throwInstantiationError();
}
defineProperties(TerrainData.prototype, {
/**
* The water mask included in this terrain data, if any. A water mask is a rectangular
* Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
* Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
* @memberof TerrainData.prototype
* @type {Uint8Array|Image|Canvas}
*/
waterMask : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Computes the terrain height at a specified longitude and latitude.
* @function
*
* @param {Rectangle} rectangle The rectangle covered by this terrain data.
* @param {Number} longitude The longitude in radians.
* @param {Number} latitude The latitude in radians.
* @returns {Number} The terrain height at the specified position. If the position
* is outside the rectangle, this method will extrapolate the height, which is likely to be wildly
* incorrect for positions far outside the rectangle.
*/
TerrainData.prototype.interpolateHeight = DeveloperError.throwInstantiationError;
/**
* Determines if a given child tile is available, based on the
* {@link TerrainData#childTileMask}. The given child tile coordinates are assumed
* to be one of the four children of this tile. If non-child tile coordinates are
* given, the availability of the southeast child tile is returned.
* @function
*
* @param {Number} thisX The tile X coordinate of this (the parent) tile.
* @param {Number} thisY The tile Y coordinate of this (the parent) tile.
* @param {Number} childX The tile X coordinate of the child tile to check for availability.
* @param {Number} childY The tile Y coordinate of the child tile to check for availability.
* @returns {Boolean} True if the child tile is available; otherwise, false.
*/
TerrainData.prototype.isChildAvailable = DeveloperError.throwInstantiationError;
/**
* Creates a {@link TerrainMesh} from this terrain data.
* @function
*
* @private
*
* @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
* @param {Number} x The X coordinate of the tile for which to create the terrain data.
* @param {Number} y The Y coordinate of the tile for which to create the terrain data.
* @param {Number} level The level of the tile for which to create the terrain data.
* @returns {Promise.|undefined} A promise for the terrain mesh, or undefined if too many
* asynchronous mesh creations are already in progress and the operation should
* be retried later.
*/
TerrainData.prototype.createMesh = DeveloperError.throwInstantiationError;
/**
* Upsamples this terrain data for use by a descendant tile.
* @function
*
* @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
* @param {Number} thisX The X coordinate of this tile in the tiling scheme.
* @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
* @param {Number} thisLevel The level of this tile in the tiling scheme.
* @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
* @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
* @returns {Promise.|undefined} A promise for upsampled terrain data for the descendant tile,
* or undefined if too many asynchronous upsample operations are in progress and the request has been
* deferred.
*/
TerrainData.prototype.upsample = DeveloperError.throwInstantiationError;
/**
* Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
* terrain data. If this value is false, the data was obtained from some other source, such
* as by downloading it from a remote server. This method should return true for instances
* returned from a call to {@link TerrainData#upsample}.
* @function
*
* @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
*/
TerrainData.prototype.wasCreatedByUpsampling = DeveloperError.throwInstantiationError;
return TerrainData;
});
/*global define*/
define('Core/TilingScheme',[
'./defineProperties',
'./DeveloperError'
], function(
defineProperties,
DeveloperError) {
'use strict';
/**
* A tiling scheme for geometry or imagery on the surface of an ellipsoid. At level-of-detail zero,
* the coarsest, least-detailed level, the number of tiles is configurable.
* At level of detail one, each of the level zero tiles has four children, two in each direction.
* At level of detail two, each of the level one tiles has four children, two in each direction.
* This continues for as many levels as are present in the geometry or imagery source.
*
* @alias TilingScheme
* @constructor
*
* @see WebMercatorTilingScheme
* @see GeographicTilingScheme
*/
function TilingScheme(options) {
throw new DeveloperError('This type should not be instantiated directly. Instead, use WebMercatorTilingScheme or GeographicTilingScheme.');
}
defineProperties(TilingScheme.prototype, {
/**
* Gets the ellipsoid that is tiled by the tiling scheme.
* @memberof TilingScheme.prototype
* @type {Ellipsoid}
*/
ellipsoid: {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the rectangle, in radians, covered by this tiling scheme.
* @memberof TilingScheme.prototype
* @type {Rectangle}
*/
rectangle : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the map projection used by the tiling scheme.
* @memberof TilingScheme.prototype
* @type {MapProjection}
*/
projection : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Gets the total number of tiles in the X direction at a specified level-of-detail.
* @function
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the X direction at the given level.
*/
TilingScheme.prototype.getNumberOfXTilesAtLevel = DeveloperError.throwInstantiationError;
/**
* Gets the total number of tiles in the Y direction at a specified level-of-detail.
* @function
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the Y direction at the given level.
*/
TilingScheme.prototype.getNumberOfYTilesAtLevel = DeveloperError.throwInstantiationError;
/**
* Transforms an rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
* @function
*
* @param {Rectangle} rectangle The rectangle to transform.
* @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
* is undefined.
*/
TilingScheme.prototype.rectangleToNativeRectangle = DeveloperError.throwInstantiationError;
/**
* Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
* of the tiling scheme.
* @function
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
TilingScheme.prototype.tileXYToNativeRectangle = DeveloperError.throwInstantiationError;
/**
* Converts tile x, y coordinates and level to a cartographic rectangle in radians.
* @function
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
TilingScheme.prototype.tileXYToRectangle = DeveloperError.throwInstantiationError;
/**
* Calculates the tile x, y coordinates of the tile containing
* a given cartographic position.
* @function
*
* @param {Cartographic} position The position.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
* if 'result' is undefined.
*/
TilingScheme.prototype.positionToTileXY = DeveloperError.throwInstantiationError;
return TilingScheme;
});
/*global define*/
define('Core/TimeIntervalCollection',[
'./binarySearch',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Event',
'./JulianDate',
'./TimeInterval'
], function(
binarySearch,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
JulianDate,
TimeInterval) {
'use strict';
function compareIntervalStartTimes(left, right) {
return JulianDate.compare(left.start, right.start);
}
/**
* A non-overlapping collection of {@link TimeInterval} instances sorted by start time.
* @alias TimeIntervalCollection
* @constructor
*
* @param {TimeInterval[]} [intervals] An array of intervals to add to the collection.
*/
function TimeIntervalCollection(intervals) {
this._intervals = [];
this._changedEvent = new Event();
if (defined(intervals)) {
var length = intervals.length;
for (var i = 0; i < length; i++) {
this.addInterval(intervals[i]);
}
}
}
defineProperties(TimeIntervalCollection.prototype, {
/**
* Gets an event that is raised whenever the collection of intervals change.
* @memberof TimeIntervalCollection.prototype
* @type {Event}
* @readonly
*/
changedEvent : {
get : function() {
return this._changedEvent;
}
},
/**
* Gets the start time of the collection.
* @memberof TimeIntervalCollection.prototype
* @type {JulianDate}
* @readonly
*/
start : {
get : function() {
var intervals = this._intervals;
return intervals.length === 0 ? undefined : intervals[0].start;
}
},
/**
* Gets whether or not the start time is included in the collection.
* @memberof TimeIntervalCollection.prototype
* @type {Boolean}
* @readonly
*/
isStartIncluded : {
get : function() {
var intervals = this._intervals;
return intervals.length === 0 ? false : intervals[0].isStartIncluded;
}
},
/**
* Gets the stop time of the collection.
* @memberof TimeIntervalCollection.prototype
* @type {JulianDate}
* @readonly
*/
stop : {
get : function() {
var intervals = this._intervals;
var length = intervals.length;
return length === 0 ? undefined : intervals[length - 1].stop;
}
},
/**
* Gets whether or not the stop time is included in the collection.
* @memberof TimeIntervalCollection.prototype
* @type {Boolean}
* @readonly
*/
isStopIncluded : {
get : function() {
var intervals = this._intervals;
var length = intervals.length;
return length === 0 ? false : intervals[length - 1].isStopIncluded;
}
},
/**
* Gets the number of intervals in the collection.
* @memberof TimeIntervalCollection.prototype
* @type {Number}
* @readonly
*/
length : {
get : function() {
return this._intervals.length;
}
},
/**
* Gets whether or not the collection is empty.
* @memberof TimeIntervalCollection.prototype
* @type {Boolean}
* @readonly
*/
isEmpty : {
get : function() {
return this._intervals.length === 0;
}
}
});
/**
* Compares this instance against the provided instance componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {TimeIntervalCollection} [right] The right hand side collection.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
TimeIntervalCollection.prototype.equals = function(right, dataComparer) {
if (this === right) {
return true;
}
if (!(right instanceof TimeIntervalCollection)) {
return false;
}
var intervals = this._intervals;
var rightIntervals = right._intervals;
var length = intervals.length;
if (length !== rightIntervals.length) {
return false;
}
for (var i = 0; i < length; i++) {
if (!TimeInterval.equals(intervals[i], rightIntervals[i], dataComparer)) {
return false;
}
}
return true;
};
/**
* Gets the interval at the specified index.
*
* @param {Number} index The index of the interval to retrieve.
* @returns {TimeInterval} The interval at the specified index, or undefined
if no interval exists as that index.
*/
TimeIntervalCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._intervals[index];
};
/**
* Removes all intervals from the collection.
*/
TimeIntervalCollection.prototype.removeAll = function() {
if (this._intervals.length > 0) {
this._intervals.length = 0;
this._changedEvent.raiseEvent(this);
}
};
/**
* Finds and returns the interval that contains the specified date.
*
* @param {JulianDate} date The date to search for.
* @returns {TimeInterval|undefined} The interval containing the specified date, undefined
if no such interval exists.
*/
TimeIntervalCollection.prototype.findIntervalContainingDate = function(date) {
var index = this.indexOf(date);
return index >= 0 ? this._intervals[index] : undefined;
};
/**
* Finds and returns the data for the interval that contains the specified date.
*
* @param {JulianDate} date The date to search for.
* @returns {Object} The data for the interval containing the specified date, or undefined
if no such interval exists.
*/
TimeIntervalCollection.prototype.findDataForIntervalContainingDate = function(date) {
var index = this.indexOf(date);
return index >= 0 ? this._intervals[index].data : undefined;
};
/**
* Checks if the specified date is inside this collection.
*
* @param {JulianDate} julianDate The date to check.
* @returns {Boolean} true
if the collection contains the specified date, false
otherwise.
*/
TimeIntervalCollection.prototype.contains = function(date) {
return this.indexOf(date) >= 0;
};
var indexOfScratch = new TimeInterval();
/**
* Finds and returns the index of the interval in the collection that contains the specified date.
*
* @param {JulianDate} date The date to search for.
* @returns {Number} The index of the interval that contains the specified date, if no such interval exists,
* it returns a negative number which is the bitwise complement of the index of the next interval that
* starts after the date, or if no interval starts after the specified date, the bitwise complement of
* the length of the collection.
*/
TimeIntervalCollection.prototype.indexOf = function(date) {
if (!defined(date)) {
throw new DeveloperError('date is required');
}
var intervals = this._intervals;
indexOfScratch.start = date;
indexOfScratch.stop = date;
var index = binarySearch(intervals, indexOfScratch, compareIntervalStartTimes);
if (index >= 0) {
if (intervals[index].isStartIncluded) {
return index;
}
if (index > 0 && intervals[index - 1].stop.equals(date) && intervals[index - 1].isStopIncluded) {
return index - 1;
}
return ~index;
}
index = ~index;
if (index > 0 && (index - 1) < intervals.length && TimeInterval.contains(intervals[index - 1], date)) {
return index - 1;
}
return ~index;
};
/**
* Returns the first interval in the collection that matches the specified parameters.
* All parameters are optional and undefined
parameters are treated as a don't care condition.
*
* @param {Object} [options] Object with the following properties:
* @param {JulianDate} [options.start] The start time of the interval.
* @param {JulianDate} [options.stop] The stop time of the interval.
* @param {Boolean} [options.isStartIncluded] true
if options.start
is included in the interval, false
otherwise.
* @param {Boolean} [options.isStopIncluded] true
if options.stop
is included in the interval, false
otherwise.
* @returns {TimeInterval} The first interval in the collection that matches the specified parameters.
*/
TimeIntervalCollection.prototype.findInterval = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var start = options.start;
var stop = options.stop;
var isStartIncluded = options.isStartIncluded;
var isStopIncluded = options.isStopIncluded;
var intervals = this._intervals;
for (var i = 0, len = intervals.length; i < len; i++) {
var interval = intervals[i];
if ((!defined(start) || interval.start.equals(start)) &&
(!defined(stop) || interval.stop.equals(stop)) &&
(!defined(isStartIncluded) || interval.isStartIncluded === isStartIncluded) &&
(!defined(isStopIncluded) || interval.isStopIncluded === isStopIncluded)) {
return intervals[i];
}
}
return undefined;
};
/**
* Adds an interval to the collection, merging intervals that contain the same data and
* splitting intervals of different data as needed in order to maintain a non-overlapping collection.
* The data in the new interval takes precedence over any existing intervals in the collection.
*
* @param {TimeInterval} interval The interval to add.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
*/
TimeIntervalCollection.prototype.addInterval = function(interval, dataComparer) {
if (!defined(interval)) {
throw new DeveloperError("interval is required");
}
if (interval.isEmpty) {
return;
}
var comparison;
var index;
var intervals = this._intervals;
// Handle the common case quickly: we're adding a new interval which is after all existing intervals.
if (intervals.length === 0 || JulianDate.greaterThan(interval.start, intervals[intervals.length - 1].stop)) {
intervals.push(interval);
this._changedEvent.raiseEvent(this);
return;
}
// Keep the list sorted by the start date
index = binarySearch(intervals, interval, compareIntervalStartTimes);
if (index < 0) {
index = ~index;
} else {
// interval's start date exactly equals the start date of at least one interval in the collection.
// It could actually equal the start date of two intervals if one of them does not actually
// include the date. In that case, the binary search could have found either. We need to
// look at the surrounding intervals and their IsStartIncluded properties in order to make sure
// we're working with the correct interval.
if (index > 0 && interval.isStartIncluded && intervals[index - 1].isStartIncluded && intervals[index - 1].start.equals(interval.start)) {
--index;
} else if (index < intervals.length && !interval.isStartIncluded && intervals[index].isStartIncluded && intervals[index].start.equals(interval.start)) {
++index;
}
}
if (index > 0) {
// Not the first thing in the list, so see if the interval before this one
// overlaps this one.
comparison = JulianDate.compare(intervals[index - 1].stop, interval.start);
if (comparison > 0 || (comparison === 0 && (intervals[index - 1].isStopIncluded || interval.isStartIncluded))) {
// There is an overlap
if (defined(dataComparer) ? dataComparer(intervals[index - 1].data, interval.data) : (intervals[index - 1].data === interval.data)) {
// Overlapping intervals have the same data, so combine them
if (JulianDate.greaterThan(interval.stop, intervals[index - 1].stop)) {
interval = new TimeInterval({
start : intervals[index - 1].start,
stop : interval.stop,
isStartIncluded : intervals[index - 1].isStartIncluded,
isStopIncluded : interval.isStopIncluded,
data : interval.data
});
} else {
interval = new TimeInterval({
start : intervals[index - 1].start,
stop : intervals[index - 1].stop,
isStartIncluded : intervals[index - 1].isStartIncluded,
isStopIncluded : intervals[index - 1].isStopIncluded || (interval.stop.equals(intervals[index - 1].stop) && interval.isStopIncluded),
data : interval.data
});
}
intervals.splice(index - 1, 1);
--index;
} else {
// Overlapping intervals have different data. The new interval
// being added 'wins' so truncate the previous interval.
// If the existing interval extends past the end of the new one,
// split the existing interval into two intervals.
comparison = JulianDate.compare(intervals[index - 1].stop, interval.stop);
if (comparison > 0 || (comparison === 0 && intervals[index - 1].isStopIncluded && !interval.isStopIncluded)) {
intervals.splice(index - 1, 1, new TimeInterval({
start : intervals[index - 1].start,
stop : interval.start,
isStartIncluded : intervals[index - 1].isStartIncluded,
isStopIncluded : !interval.isStartIncluded,
data : intervals[index - 1].data
}), new TimeInterval({
start : interval.stop,
stop : intervals[index - 1].stop,
isStartIncluded : !interval.isStopIncluded,
isStopIncluded : intervals[index - 1].isStopIncluded,
data : intervals[index - 1].data
}));
} else {
intervals[index - 1] = new TimeInterval({
start : intervals[index - 1].start,
stop : interval.start,
isStartIncluded : intervals[index - 1].isStartIncluded,
isStopIncluded : !interval.isStartIncluded,
data : intervals[index - 1].data
});
}
}
}
}
while (index < intervals.length) {
// Not the last thing in the list, so see if the intervals after this one overlap this one.
comparison = JulianDate.compare(interval.stop, intervals[index].start);
if (comparison > 0 || (comparison === 0 && (interval.isStopIncluded || intervals[index].isStartIncluded))) {
// There is an overlap
if (defined(dataComparer) ? dataComparer(intervals[index].data, interval.data) : intervals[index].data === interval.data) {
// Overlapping intervals have the same data, so combine them
interval = new TimeInterval({
start : interval.start,
stop : JulianDate.greaterThan(intervals[index].stop, interval.stop) ? intervals[index].stop : interval.stop,
isStartIncluded : interval.isStartIncluded,
isStopIncluded : JulianDate.greaterThan(intervals[index].stop, interval.stop) ? intervals[index].isStopIncluded : interval.isStopIncluded,
data : interval.data
});
intervals.splice(index, 1);
} else {
// Overlapping intervals have different data. The new interval
// being added 'wins' so truncate the next interval.
intervals[index] = new TimeInterval({
start : interval.stop,
stop : intervals[index].stop,
isStartIncluded : !interval.isStopIncluded,
isStopIncluded : intervals[index].isStopIncluded,
data : intervals[index].data
});
if (intervals[index].isEmpty) {
intervals.splice(index, 1);
} else {
// Found a partial span, so it is not possible for the next
// interval to be spanned at all. Stop looking.
break;
}
}
} else {
// Found the last one we're spanning, so stop looking.
break;
}
}
// Add the new interval
intervals.splice(index, 0, interval);
this._changedEvent.raiseEvent(this);
};
/**
* Removes the specified interval from this interval collection, creating a hole over the specified interval.
* The data property of the input interval is ignored.
*
* @param {TimeInterval} interval The interval to remove.
* @returns true
if the interval was removed, false
if no part of the interval was in the collection.
*/
TimeIntervalCollection.prototype.removeInterval = function(interval) {
if (!defined(interval)) {
throw new DeveloperError("interval is required");
}
if (interval.isEmpty) {
return false;
}
var result = false;
var intervals = this._intervals;
var index = binarySearch(intervals, interval, compareIntervalStartTimes);
if (index < 0) {
index = ~index;
}
var intervalStart = interval.start;
var intervalStop = interval.stop;
var intervalIsStartIncluded = interval.isStartIncluded;
var intervalIsStopIncluded = interval.isStopIncluded;
// Check for truncation of the end of the previous interval.
if (index > 0) {
var indexMinus1 = intervals[index - 1];
var indexMinus1Stop = indexMinus1.stop;
if (JulianDate.greaterThan(indexMinus1Stop, intervalStart) ||
(TimeInterval.equals(indexMinus1Stop, intervalStart) &&
indexMinus1.isStopIncluded && intervalIsStartIncluded)) {
result = true;
if (JulianDate.greaterThan(indexMinus1Stop, intervalStop) ||
(indexMinus1.isStopIncluded && !intervalIsStopIncluded && TimeInterval.equals(indexMinus1Stop, intervalStop))) {
// Break the existing interval into two pieces
intervals.splice(index, 0, new TimeInterval({
start : intervalStop,
stop : indexMinus1Stop,
isStartIncluded : !intervalIsStopIncluded,
isStopIncluded : indexMinus1.isStopIncluded,
data : indexMinus1.data
}));
}
intervals[index - 1] = new TimeInterval({
start : indexMinus1.start,
stop : intervalStart,
isStartIncluded : indexMinus1.isStartIncluded,
isStopIncluded : !intervalIsStartIncluded,
data : indexMinus1.data
});
}
}
// Check if the Start of the current interval should remain because interval.start is the same but
// it is not included.
var indexInterval = intervals[index];
if (index < intervals.length &&
!intervalIsStartIncluded &&
indexInterval.isStartIncluded &&
intervalStart.equals(indexInterval.start)) {
result = true;
intervals.splice(index, 0, new TimeInterval({
start : indexInterval.start,
stop : indexInterval.start,
isStartIncluded : true,
isStopIncluded : true,
data : indexInterval.data
}));
++index;
indexInterval = intervals[index];
}
// Remove any intervals that are completely overlapped by the input interval.
while (index < intervals.length && JulianDate.greaterThan(intervalStop, indexInterval.stop)) {
result = true;
intervals.splice(index, 1);
indexInterval = intervals[index];
}
// Check for the case where the input interval ends on the same date
// as an existing interval.
if (index < intervals.length && intervalStop.equals(indexInterval.stop)) {
result = true;
if (!intervalIsStopIncluded && indexInterval.isStopIncluded) {
// Last point of interval should remain because the stop date is included in
// the existing interval but is not included in the input interval.
if ((index + 1) < intervals.length && intervals[index + 1].start.equals(intervalStop) && indexInterval.data === intervals[index + 1].data) {
// Combine single point with the next interval
intervals.splice(index, 1);
indexInterval = new TimeInterval({
start : indexInterval.start,
stop : indexInterval.stop,
isStartIncluded : true,
isStopIncluded : indexInterval.isStopIncluded,
data : indexInterval.data
});
} else {
indexInterval = new TimeInterval({
start : intervalStop,
stop : intervalStop,
isStartIncluded : true,
isStopIncluded : true,
data : indexInterval.data
});
}
intervals[index] = indexInterval;
} else {
// Interval is completely overlapped
intervals.splice(index, 1);
}
}
// Truncate any partially-overlapped intervals.
if (index < intervals.length &&
(JulianDate.greaterThan(intervalStop, indexInterval.start) ||
(intervalStop.equals(indexInterval.start) &&
intervalIsStopIncluded &&
indexInterval.isStartIncluded))) {
result = true;
intervals[index] = new TimeInterval({
start : intervalStop,
stop : indexInterval.stop,
isStartIncluded : !intervalIsStopIncluded,
isStopIncluded : indexInterval.isStopIncluded,
data : indexInterval.data
});
}
if (result) {
this._changedEvent.raiseEvent(this);
}
return result;
};
/**
* Creates a new instance that is the intersection of this collection and the provided collection.
*
* @param {TimeIntervalCollection} other The collection to intersect with.
* @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
* @param {TimeInterval~MergeCallback} [mergeCallback] A function which merges the data of the two intervals. If omitted, the data from the left interval will be used.
* @returns {TimeIntervalCollection} A new TimeIntervalCollection which is the intersection of this collection and the provided collection.
*/
TimeIntervalCollection.prototype.intersect = function(other, dataComparer, mergeCallback) {
if (!defined(other)) {
throw new DeveloperError('other is required.');
}
var left = 0;
var right = 0;
var result = new TimeIntervalCollection();
var intervals = this._intervals;
var otherIntervals = other._intervals;
while (left < intervals.length && right < otherIntervals.length) {
var leftInterval = intervals[left];
var rightInterval = otherIntervals[right];
if (JulianDate.lessThan(leftInterval.stop, rightInterval.start)) {
++left;
} else if (JulianDate.lessThan(rightInterval.stop, leftInterval.start)) {
++right;
} else {
// The following will return an intersection whose data is 'merged' if the callback is defined
if (defined(mergeCallback) ||
((defined(dataComparer) && dataComparer(leftInterval.data, rightInterval.data)) ||
(!defined(dataComparer) && rightInterval.data === leftInterval.data))) {
var intersection = TimeInterval.intersect(leftInterval, rightInterval, new TimeInterval(), mergeCallback);
if (!intersection.isEmpty) {
// Since we start with an empty collection for 'result', and there are no overlapping intervals in 'this' (as a rule),
// the 'intersection' will never overlap with a previous interval in 'result'. So, no need to do any additional 'merging'.
result.addInterval(intersection, dataComparer);
}
}
if (JulianDate.lessThan(leftInterval.stop, rightInterval.stop) ||
(leftInterval.stop.equals(rightInterval.stop) &&
!leftInterval.isStopIncluded &&
rightInterval.isStopIncluded)) {
++left;
} else {
++right;
}
}
}
return result;
};
return TimeIntervalCollection;
});
/*global define*/
define('Core/TranslationRotationScale',[
'./Cartesian3',
'./defaultValue',
'./defined',
'./Quaternion'
], function(
Cartesian3,
defaultValue,
defined,
Quaternion) {
'use strict';
var defaultScale = new Cartesian3(1.0, 1.0, 1.0);
var defaultTranslation = Cartesian3.ZERO;
var defaultRotation = Quaternion.IDENTITY;
/**
* An affine transformation defined by a translation, rotation, and scale.
* @alias TranslationRotationScale
* @constructor
*
* @param {Cartesian3} [translation=Cartesian3.ZERO] A {@link Cartesian3} specifying the (x, y, z) translation to apply to the node.
* @param {Quaternion} [rotation=Quaternion.IDENTITY] A {@link Quaternion} specifying the (x, y, z, w) rotation to apply to the node.
* @param {Cartesian3} [scale=new Cartesian3(1.0, 1.0, 1.0)] A {@link Cartesian3} specifying the (x, y, z) scaling to apply to the node.
*/
var TranslationRotationScale = function(translation, rotation, scale) {
/**
* Gets or sets the (x, y, z) translation to apply to the node.
* @type {Cartesian3}
* @default Cartesian3.ZERO
*/
this.translation = Cartesian3.clone(defaultValue(translation, defaultTranslation));
/**
* Gets or sets the (x, y, z, w) rotation to apply to the node.
* @type {Quaternion}
* @default Quaternion.IDENTITY
*/
this.rotation = Quaternion.clone(defaultValue(rotation, defaultRotation));
/**
* Gets or sets the (x, y, z) scaling to apply to the node.
* @type {Cartesian3}
* @default new Cartesian3(1.0, 1.0, 1.0)
*/
this.scale = Cartesian3.clone(defaultValue(scale, defaultScale));
};
/**
* Compares this instance against the provided instance and returns
* true
if they are equal, false
otherwise.
*
* @param {TranslationRotationScale} [right] The right hand side TranslationRotationScale.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
TranslationRotationScale.prototype.equals = function(right) {
return (this === right) ||
(defined(right) &&
Cartesian3.equals(this.translation, right.translation) &&
Quaternion.equals(this.rotation, right.rotation) &&
Cartesian3.equals(this.scale, right.scale));
};
return TranslationRotationScale;
});
/*global define*/
define('Core/VideoSynchronizer',[
'./defaultValue',
'./defined',
'./defineProperties',
'./destroyObject',
'./Iso8601',
'./JulianDate'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
Iso8601,
JulianDate) {
'use strict';
/**
* Synchronizes a video element with a simulation clock.
*
* @alias VideoSynchronizer
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Clock} [options.clock] The clock instance used to drive the video.
* @param {HTMLVideoElement} [options.element] The video element to be synchronized.
* @param {JulianDate} [options.epoch=Iso8601.MINIMUM_VALUE] The simulation time that marks the start of the video.
* @param {Number} [options.tolerance=1.0] The maximum amount of time, in seconds, that the clock and video can diverge.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Video.html|Video Material Demo}
*/
function VideoSynchronizer(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._clock = undefined;
this._element = undefined;
this._clockSubscription = undefined;
this._seekFunction = undefined;
this.clock = options.clock;
this.element = options.element;
/**
* Gets or sets the simulation time that marks the start of the video.
* @type {JulianDate}
* @default Iso8601.MINIMUM_VALUE
*/
this.epoch = defaultValue(options.epoch, Iso8601.MINIMUM_VALUE);
/**
* Gets or sets the amount of time in seconds the video's currentTime
* and the clock's currentTime can diverge before a video seek is performed.
* Lower values make the synchronization more accurate but video
* performance might suffer. Higher values provide better performance
* but at the cost of accuracy.
* @type {Number}
* @default 1.0
*/
this.tolerance = defaultValue(options.tolerance, 1.0);
this._seeking = false;
this._seekFunction = undefined;
this._firstTickAfterSeek = false;
}
defineProperties(VideoSynchronizer.prototype, {
/**
* Gets or sets the clock used to drive the video element.
*
* @memberof VideoSynchronizer.prototype
* @type {Clock}
*/
clock : {
get : function() {
return this._clock;
},
set : function(value) {
var oldValue = this._clock;
if (oldValue === value) {
return;
}
if (defined(oldValue)) {
this._clockSubscription();
this._clockSubscription = undefined;
}
if (defined(value)) {
this._clockSubscription = value.onTick.addEventListener(VideoSynchronizer.prototype._onTick, this);
}
this._clock = value;
}
},
/**
* Gets or sets the video element to synchronize.
*
* @memberof VideoSynchronizer.prototype
* @type {HTMLVideoElement}
*/
element : {
get : function() {
return this._element;
},
set : function(value) {
var oldValue = this._element;
if (oldValue === value) {
return;
}
if (defined(oldValue)) {
oldValue.removeEventListener("seeked", this._seekFunction, false);
}
if (defined(value)) {
this._seeking = false;
this._seekFunction = createSeekFunction(this);
value.addEventListener("seeked", this._seekFunction, false);
}
this._element = value;
this._seeking = false;
this._firstTickAfterSeek = false;
}
}
});
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
VideoSynchronizer.prototype.destroy = function() {
this.element = undefined;
this.clock = undefined;
return destroyObject(this);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
VideoSynchronizer.prototype.isDestroyed = function() {
return false;
};
VideoSynchronizer.prototype._onTick = function(clock) {
var element = this._element;
if (!defined(element) || element.readyState < 2) {
return;
}
var paused = element.paused;
var shouldAnimate = clock.shouldAnimate;
if (shouldAnimate === paused) {
if (shouldAnimate) {
element.play();
} else {
element.pause();
}
}
//We need to avoid constant seeking or the video will
//never contain a complete frame for us to render.
//So don't do anything if we're seeing or on the first
//tick after a seek (the latter of which allows the frame
//to actually be rendered.
if (this._seeking || this._firstTickAfterSeek) {
this._firstTickAfterSeek = false;
return;
}
element.playbackRate = clock.multiplier;
var clockTime = clock.currentTime;
var epoch = defaultValue(this.epoch, Iso8601.MINIMUM_VALUE);
var videoTime = JulianDate.secondsDifference(clockTime, epoch);
var duration = element.duration;
var desiredTime;
var currentTime = element.currentTime;
if (element.loop) {
videoTime = videoTime % duration;
if (videoTime < 0.0) {
videoTime = duration - videoTime;
}
desiredTime = videoTime;
} else if (videoTime > duration) {
desiredTime = duration;
} else if (videoTime < 0.0) {
desiredTime = 0.0;
} else {
desiredTime = videoTime;
}
//If the playing video's time and the scene's clock time
//ever drift too far apart, we want to set the video to match
var tolerance = shouldAnimate ? defaultValue(this.tolerance, 1.0) : 0.001;
if (Math.abs(desiredTime - currentTime) > tolerance) {
this._seeking = true;
element.currentTime = desiredTime;
}
};
function createSeekFunction(that) {
return function() {
that._seeking = false;
that._firstTickAfterSeek = true;
};
}
return VideoSynchronizer;
});
/*global define*/
define('Core/VRTheWorldTerrainProvider',[
'../ThirdParty/when',
'./Credit',
'./defaultValue',
'./defined',
'./defineProperties',
'./DeveloperError',
'./Ellipsoid',
'./Event',
'./GeographicTilingScheme',
'./getImagePixels',
'./HeightmapTerrainData',
'./loadImage',
'./loadXML',
'./Math',
'./Rectangle',
'./TerrainProvider',
'./throttleRequestByServer',
'./TileProviderError'
], function(
when,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Event,
GeographicTilingScheme,
getImagePixels,
HeightmapTerrainData,
loadImage,
loadXML,
CesiumMath,
Rectangle,
TerrainProvider,
throttleRequestByServer,
TileProviderError) {
'use strict';
function DataRectangle(rectangle, maxLevel) {
this.rectangle = rectangle;
this.maxLevel = maxLevel;
}
/**
* A {@link TerrainProvider} that produces terrain geometry by tessellating height maps
* retrieved from a {@link http://vr-theworld.com/|VT MÄK VR-TheWorld server}.
*
* @alias VRTheWorldTerrainProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The URL of the VR-TheWorld TileMap.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid. If this parameter is not
* specified, the WGS84 ellipsoid is used.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
*
*
* @example
* var terrainProvider = new Cesium.VRTheWorldTerrainProvider({
* url : 'https://www.vr-theworld.com/vr-theworld/tiles1.0.0/73/'
* });
* viewer.terrainProvider = terrainProvider;
*
* @see TerrainProvider
*/
function VRTheWorldTerrainProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
this._url = options.url;
if (this._url.length > 0 && this._url[this._url.length - 1] !== '/') {
this._url += '/';
}
this._errorEvent = new Event();
this._ready = false;
this._readyPromise = when.defer();
this._proxy = options.proxy;
this._terrainDataStructure = {
heightScale : 1.0 / 1000.0,
heightOffset : -1000.0,
elementsPerHeight : 3,
stride : 4,
elementMultiplier : 256.0,
isBigEndian : true,
lowestEncodedHeight : 0,
highestEncodedHeight : 256 * 256 * 256 - 1
};
var credit = options.credit;
if (typeof credit === 'string') {
credit = new Credit(credit);
}
this._credit = credit;
this._tilingScheme = undefined;
this._rectangles = [];
var that = this;
var metadataError;
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
function metadataSuccess(xml) {
var srs = xml.getElementsByTagName('SRS')[0].textContent;
if (srs === 'EPSG:4326') {
that._tilingScheme = new GeographicTilingScheme({ ellipsoid : ellipsoid });
} else {
metadataFailure('SRS ' + srs + ' is not supported.');
return;
}
var tileFormat = xml.getElementsByTagName('TileFormat')[0];
that._heightmapWidth = parseInt(tileFormat.getAttribute('width'), 10);
that._heightmapHeight = parseInt(tileFormat.getAttribute('height'), 10);
that._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, Math.min(that._heightmapWidth, that._heightmapHeight), that._tilingScheme.getNumberOfXTilesAtLevel(0));
var dataRectangles = xml.getElementsByTagName('DataExtent');
for (var i = 0; i < dataRectangles.length; ++i) {
var dataRectangle = dataRectangles[i];
var west = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('minx')));
var south = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('miny')));
var east = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('maxx')));
var north = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('maxy')));
var maxLevel = parseInt(dataRectangle.getAttribute('maxlevel'), 10);
that._rectangles.push(new DataRectangle(new Rectangle(west, south, east, north), maxLevel));
}
that._ready = true;
that._readyPromise.resolve(true);
}
function metadataFailure(e) {
var message = defaultValue(e, 'An error occurred while accessing ' + that._url + '.');
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
}
function requestMetadata() {
when(loadXML(that._url), metadataSuccess, metadataFailure);
}
requestMetadata();
}
defineProperties(VRTheWorldTerrainProvider.prototype, {
/**
* Gets an event that is raised when the terrain provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets the credit to display when this terrain provider is active. Typically this is used to credit
* the source of the terrain. This function should not be called before {@link VRTheWorldTerrainProvider#ready} returns true.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {Credit}
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link VRTheWorldTerrainProvider#ready} returns true.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {GeographicTilingScheme}
*/
tilingScheme : {
get : function() {
if (!this.ready) {
throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
}
return this._tilingScheme;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {Boolean}
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Gets a value indicating whether or not the provider includes a water mask. The water mask
* indicates which areas of the globe are water rather than land, so they can be rendered
* as a reflective surface with animated waves. This function should not be
* called before {@link VRTheWorldTerrainProvider#ready} returns true.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {Boolean}
*/
hasWaterMask : {
get : function() {
return false;
}
},
/**
* Gets a value indicating whether or not the requested tiles include vertex normals.
* This function should not be called before {@link VRTheWorldTerrainProvider#ready} returns true.
* @memberof VRTheWorldTerrainProvider.prototype
* @type {Boolean}
*/
hasVertexNormals : {
get : function() {
return false;
}
}
});
/**
* Requests the geometry for a given tile. This function should not be called before
* {@link VRTheWorldTerrainProvider#ready} returns true. The result includes terrain
* data and indicates that all child tiles are available.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
* or false if the request should be initiated regardless of the number of requests
* already in progress.
* @returns {Promise.|undefined} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
if (!this.ready) {
throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
}
var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);
var url = this._url + level + '/' + x + '/' + (yTiles - y - 1) + '.tif?cesium=true';
var proxy = this._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
var promise;
throttleRequests = defaultValue(throttleRequests, true);
if (throttleRequests) {
promise = throttleRequestByServer(url, loadImage);
if (!defined(promise)) {
return undefined;
}
} else {
promise = loadImage(url);
}
var that = this;
return when(promise, function(image) {
return new HeightmapTerrainData({
buffer : getImagePixels(image),
width : that._heightmapWidth,
height : that._heightmapHeight,
childTileMask : getChildMask(that, x, y, level),
structure : that._terrainDataStructure
});
});
};
/**
* Gets the maximum geometric error allowed in a tile at a given level.
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error.
*/
VRTheWorldTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
if (!this.ready) {
throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
}
return this._levelZeroMaximumGeometricError / (1 << level);
};
var rectangleScratch = new Rectangle();
function getChildMask(provider, x, y, level) {
var tilingScheme = provider._tilingScheme;
var rectangles = provider._rectangles;
var parentRectangle = tilingScheme.tileXYToRectangle(x, y, level);
var childMask = 0;
for (var i = 0; i < rectangles.length && childMask !== 15; ++i) {
var rectangle = rectangles[i];
if (rectangle.maxLevel <= level) {
continue;
}
var testRectangle = rectangle.rectangle;
var intersection = Rectangle.intersection(testRectangle, parentRectangle, rectangleScratch);
if (defined(intersection)) {
// Parent tile is inside this rectangle, so at least one child is, too.
if (isTileInRectangle(tilingScheme, testRectangle, x * 2, y * 2, level + 1)) {
childMask |= 4; // northwest
}
if (isTileInRectangle(tilingScheme, testRectangle, x * 2 + 1, y * 2, level + 1)) {
childMask |= 8; // northeast
}
if (isTileInRectangle(tilingScheme, testRectangle, x * 2, y * 2 + 1, level + 1)) {
childMask |= 1; // southwest
}
if (isTileInRectangle(tilingScheme, testRectangle, x * 2 + 1, y * 2 + 1, level + 1)) {
childMask |= 2; // southeast
}
}
}
return childMask;
}
function isTileInRectangle(tilingScheme, rectangle, x, y, level) {
var tileRectangle = tilingScheme.tileXYToRectangle(x, y, level);
return defined(Rectangle.intersection(tileRectangle, rectangle, rectangleScratch));
}
/**
* Determines whether data for a tile is available to be loaded.
*
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
* @returns {Boolean} Undefined if not supported, otherwise true or false.
*/
VRTheWorldTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
return undefined;
};
return VRTheWorldTerrainProvider;
});
/*global define*/
define('Core/WallGeometryLibrary',[
'./Cartographic',
'./defined',
'./EllipsoidTangentPlane',
'./Math',
'./PolygonPipeline',
'./PolylinePipeline',
'./WindingOrder'
], function(
Cartographic,
defined,
EllipsoidTangentPlane,
CesiumMath,
PolygonPipeline,
PolylinePipeline,
WindingOrder) {
'use strict';
/**
* private
*/
var WallGeometryLibrary = {};
function latLonEquals(c0, c1) {
return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON14)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON14)));
}
var scratchCartographic1 = new Cartographic();
var scratchCartographic2 = new Cartographic();
function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) {
var length = positions.length;
if (length < 2) {
return;
}
var hasBottomHeights = defined(bottomHeights);
var hasTopHeights = defined(topHeights);
var hasAllZeroHeights = true;
var cleanedPositions = new Array(length);
var cleanedTopHeights = new Array(length);
var cleanedBottomHeights = new Array(length);
var v0 = positions[0];
cleanedPositions[0] = v0;
var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1);
if (hasTopHeights) {
c0.height = topHeights[0];
}
hasAllZeroHeights = hasAllZeroHeights && c0.height <= 0;
cleanedTopHeights[0] = c0.height;
if (hasBottomHeights) {
cleanedBottomHeights[0] = bottomHeights[0];
} else {
cleanedBottomHeights[0] = 0.0;
}
var index = 1;
for (var i = 1; i < length; ++i) {
var v1 = positions[i];
var c1 = ellipsoid.cartesianToCartographic(v1, scratchCartographic2);
if (hasTopHeights) {
c1.height = topHeights[i];
}
hasAllZeroHeights = hasAllZeroHeights && c1.height <= 0;
if (!latLonEquals(c0, c1)) {
cleanedPositions[index] = v1; // Shallow copy!
cleanedTopHeights[index] = c1.height;
if (hasBottomHeights) {
cleanedBottomHeights[index] = bottomHeights[i];
} else {
cleanedBottomHeights[index] = 0.0;
}
Cartographic.clone(c1, c0);
++index;
} else if (c0.height < c1.height) {
cleanedTopHeights[index - 1] = c1.height;
}
}
if (hasAllZeroHeights || index < 2) {
return;
}
cleanedPositions.length = index;
cleanedTopHeights.length = index;
cleanedBottomHeights.length = index;
return {
positions: cleanedPositions,
topHeights: cleanedTopHeights,
bottomHeights: cleanedBottomHeights
};
}
var positionsArrayScratch = new Array(2);
var heightsArrayScratch = new Array(2);
var generateArcOptionsScratch = {
positions : undefined,
height : undefined,
granularity : undefined,
ellipsoid : undefined
};
/**
* @private
*/
WallGeometryLibrary.computePositions = function(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, duplicateCorners) {
var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights);
if (!defined(o)) {
return;
}
wallPositions = o.positions;
maximumHeights = o.topHeights;
minimumHeights = o.bottomHeights;
if (wallPositions.length >= 3) {
// Order positions counter-clockwise
var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid);
var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions);
if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) {
wallPositions.reverse();
maximumHeights.reverse();
minimumHeights.reverse();
}
}
var length = wallPositions.length;
var numCorners = length - 2;
var topPositions;
var bottomPositions;
var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
var generateArcOptions = generateArcOptionsScratch;
generateArcOptions.minDistance = minDistance;
generateArcOptions.ellipsoid = ellipsoid;
if (duplicateCorners) {
var count = 0;
var i;
for (i = 0; i < length - 1; i++) {
count += PolylinePipeline.numberOfPoints(wallPositions[i], wallPositions[i+1], minDistance) + 1;
}
topPositions = new Float64Array(count * 3);
bottomPositions = new Float64Array(count * 3);
var generateArcPositions = positionsArrayScratch;
var generateArcHeights = heightsArrayScratch;
generateArcOptions.positions = generateArcPositions;
generateArcOptions.height = generateArcHeights;
var offset = 0;
for (i = 0; i < length - 1; i++) {
generateArcPositions[0] = wallPositions[i];
generateArcPositions[1] = wallPositions[i + 1];
generateArcHeights[0] = maximumHeights[i];
generateArcHeights[1] = maximumHeights[i + 1];
var pos = PolylinePipeline.generateArc(generateArcOptions);
topPositions.set(pos, offset);
generateArcHeights[0] = minimumHeights[i];
generateArcHeights[1] = minimumHeights[i + 1];
bottomPositions.set(PolylinePipeline.generateArc(generateArcOptions), offset);
offset += pos.length;
}
} else {
generateArcOptions.positions = wallPositions;
generateArcOptions.height = maximumHeights;
topPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));
generateArcOptions.height = minimumHeights;
bottomPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));
}
return {
bottomPositions: bottomPositions,
topPositions: topPositions,
numCorners: numCorners
};
};
return WallGeometryLibrary;
});
/*global define*/
define('Core/WallGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PrimitiveType',
'./VertexFormat',
'./WallGeometryLibrary'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PrimitiveType,
VertexFormat,
WallGeometryLibrary) {
'use strict';
var scratchCartesian3Position1 = new Cartesian3();
var scratchCartesian3Position2 = new Cartesian3();
var scratchCartesian3Position3 = new Cartesian3();
var scratchCartesian3Position4 = new Cartesian3();
var scratchCartesian3Position5 = new Cartesian3();
var scratchBinormal = new Cartesian3();
var scratchTangent = new Cartesian3();
var scratchNormal = new Cartesian3();
/**
* A description of a wall, which is similar to a KML line string. A wall is defined by a series of points,
* which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
*
* @alias WallGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Number[]} [options.maximumHeights] An array parallel to positions
that give the maximum height of the
* wall at positions
. If undefined, the height of each position in used.
* @param {Number[]} [options.minimumHeights] An array parallel to positions
that give the minimum height of the
* wall at positions
. If undefined, the height at each position is 0.0.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
*
* @exception {DeveloperError} positions length must be greater than or equal to 2.
* @exception {DeveloperError} positions and maximumHeights must have the same length.
* @exception {DeveloperError} positions and minimumHeights must have the same length.
*
* @see WallGeometry#createGeometry
* @see WallGeometry#fromConstantHeight
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo}
*
* @example
* // create a wall that spans from ground level to 10000 meters
* var wall = new Cesium.WallGeometry({
* positions : Cesium.Cartesian3.fromDegreesArrayHeights([
* 19.0, 47.0, 10000.0,
* 19.0, 48.0, 10000.0,
* 20.0, 48.0, 10000.0,
* 20.0, 47.0, 10000.0,
* 19.0, 47.0, 10000.0
* ])
* });
* var geometry = Cesium.WallGeometry.createGeometry(wall);
*/
function WallGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var wallPositions = options.positions;
var maximumHeights = options.maximumHeights;
var minimumHeights = options.minimumHeights;
if (!defined(wallPositions)) {
throw new DeveloperError('options.positions is required.');
}
if (defined(maximumHeights) && maximumHeights.length !== wallPositions.length) {
throw new DeveloperError('options.positions and options.maximumHeights must have the same length.');
}
if (defined(minimumHeights) && minimumHeights.length !== wallPositions.length) {
throw new DeveloperError('options.positions and options.minimumHeights must have the same length.');
}
var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
this._positions = wallPositions;
this._minimumHeights = minimumHeights;
this._maximumHeights = maximumHeights;
this._vertexFormat = VertexFormat.clone(vertexFormat);
this._granularity = granularity;
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._workerName = 'createWallGeometry';
var numComponents = 1 + wallPositions.length * Cartesian3.packedLength + 2;
if (defined(minimumHeights)) {
numComponents += minimumHeights.length;
}
if (defined(maximumHeights)) {
numComponents += maximumHeights.length;
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 1;
}
/**
* Stores the provided instance into the provided array.
*
* @param {WallGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
WallGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
var minimumHeights = value._minimumHeights;
length = defined(minimumHeights) ? minimumHeights.length : 0;
array[startingIndex++] = length;
if (defined(minimumHeights)) {
for (i = 0; i < length; ++i) {
array[startingIndex++] = minimumHeights[i];
}
}
var maximumHeights = value._maximumHeights;
length = defined(maximumHeights) ? maximumHeights.length : 0;
array[startingIndex++] = length;
if (defined(maximumHeights)) {
for (i = 0; i < length; ++i) {
array[startingIndex++] = maximumHeights[i];
}
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
VertexFormat.pack(value._vertexFormat, array, startingIndex);
startingIndex += VertexFormat.packedLength;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchVertexFormat = new VertexFormat();
var scratchOptions = {
positions : undefined,
minimumHeights : undefined,
maximumHeights : undefined,
ellipsoid : scratchEllipsoid,
vertexFormat : scratchVertexFormat,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {WallGeometry} [result] The object into which to store the result.
* @returns {WallGeometry} The modified result parameter or a new WallGeometry instance if one was not provided.
*/
WallGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var length = array[startingIndex++];
var positions = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
length = array[startingIndex++];
var minimumHeights;
if (length > 0) {
minimumHeights = new Array(length);
for (i = 0; i < length; ++i) {
minimumHeights[i] = array[startingIndex++];
}
}
length = array[startingIndex++];
var maximumHeights;
if (length > 0) {
maximumHeights = new Array(length);
for (i = 0; i < length; ++i) {
maximumHeights[i] = array[startingIndex++];
}
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
startingIndex += VertexFormat.packedLength;
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.positions = positions;
scratchOptions.minimumHeights = minimumHeights;
scratchOptions.maximumHeights = maximumHeights;
scratchOptions.granularity = granularity;
return new WallGeometry(scratchOptions);
}
result._positions = positions;
result._minimumHeights = minimumHeights;
result._maximumHeights = maximumHeights;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
result._granularity = granularity;
return result;
};
/**
* A description of a wall, which is similar to a KML line string. A wall is defined by a series of points,
* which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
* @param {Number} [options.maximumHeight] A constant that defines the maximum height of the
* wall at positions
. If undefined, the height of each position in used.
* @param {Number} [options.minimumHeight] A constant that defines the minimum height of the
* wall at positions
. If undefined, the height at each position is 0.0.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
* @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
* @returns {WallGeometry}
*
*
* @example
* // create a wall that spans from 10000 meters to 20000 meters
* var wall = Cesium.WallGeometry.fromConstantHeights({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 19.0, 47.0,
* 19.0, 48.0,
* 20.0, 48.0,
* 20.0, 47.0,
* 19.0, 47.0,
* ]),
* minimumHeight : 20000.0,
* maximumHeight : 10000.0
* });
* var geometry = Cesium.WallGeometry.createGeometry(wall);
*
* @see WallGeometry#createGeometry
*/
WallGeometry.fromConstantHeights = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.positions;
if (!defined(positions)) {
throw new DeveloperError('options.positions is required.');
}
var minHeights;
var maxHeights;
var min = options.minimumHeight;
var max = options.maximumHeight;
var doMin = defined(min);
var doMax = defined(max);
if (doMin || doMax) {
var length = positions.length;
minHeights = (doMin) ? new Array(length) : undefined;
maxHeights = (doMax) ? new Array(length) : undefined;
for (var i = 0; i < length; ++i) {
if (doMin) {
minHeights[i] = min;
}
if (doMax) {
maxHeights[i] = max;
}
}
}
var newOptions = {
positions : positions,
maximumHeights : maxHeights,
minimumHeights : minHeights,
ellipsoid : options.ellipsoid,
vertexFormat : options.vertexFormat
};
return new WallGeometry(newOptions);
};
/**
* Computes the geometric representation of a wall, including its vertices, indices, and a bounding sphere.
*
* @param {WallGeometry} wallGeometry A description of the wall.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
WallGeometry.createGeometry = function(wallGeometry) {
var wallPositions = wallGeometry._positions;
var minimumHeights = wallGeometry._minimumHeights;
var maximumHeights = wallGeometry._maximumHeights;
var vertexFormat = wallGeometry._vertexFormat;
var granularity = wallGeometry._granularity;
var ellipsoid = wallGeometry._ellipsoid;
var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, true);
if (!defined(pos)) {
return;
}
var bottomPositions = pos.bottomPositions;
var topPositions = pos.topPositions;
var numCorners = pos.numCorners;
var length = topPositions.length;
var size = length * 2;
var positions = vertexFormat.position ? new Float64Array(size) : undefined;
var normals = vertexFormat.normal ? new Float32Array(size) : undefined;
var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined;
var binormals = vertexFormat.binormal ? new Float32Array(size) : undefined;
var textureCoordinates = vertexFormat.st ? new Float32Array(size / 3 * 2) : undefined;
var positionIndex = 0;
var normalIndex = 0;
var binormalIndex = 0;
var tangentIndex = 0;
var stIndex = 0;
// add lower and upper points one after the other, lower
// points being even and upper points being odd
var normal = scratchNormal;
var tangent = scratchTangent;
var binormal = scratchBinormal;
var recomputeNormal = true;
length /= 3;
var i;
var s = 0;
var ds = 1/(length - wallPositions.length + 1);
for (i = 0; i < length; ++i) {
var i3 = i * 3;
var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1);
var bottomPosition = Cartesian3.fromArray(bottomPositions, i3, scratchCartesian3Position2);
if (vertexFormat.position) {
// insert the lower point
positions[positionIndex++] = bottomPosition.x;
positions[positionIndex++] = bottomPosition.y;
positions[positionIndex++] = bottomPosition.z;
// insert the upper point
positions[positionIndex++] = topPosition.x;
positions[positionIndex++] = topPosition.y;
positions[positionIndex++] = topPosition.z;
}
if (vertexFormat.st) {
textureCoordinates[stIndex++] = s;
textureCoordinates[stIndex++] = 0.0;
textureCoordinates[stIndex++] = s;
textureCoordinates[stIndex++] = 1.0;
}
if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.binormal) {
var nextPosition;
var nextTop = Cartesian3.clone(Cartesian3.ZERO, scratchCartesian3Position5);
var groundPosition = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position2), scratchCartesian3Position2);
if (i + 1 < length) {
nextPosition = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(topPositions, i3 + 3, scratchCartesian3Position3), scratchCartesian3Position3);
nextTop = Cartesian3.fromArray(topPositions, i3 + 3, scratchCartesian3Position5);
}
if (recomputeNormal) {
var scalednextPosition = Cartesian3.subtract(nextTop, topPosition, scratchCartesian3Position4);
var scaledGroundPosition = Cartesian3.subtract(groundPosition, topPosition, scratchCartesian3Position1);
normal = Cartesian3.normalize(Cartesian3.cross(scaledGroundPosition, scalednextPosition, normal), normal);
recomputeNormal = false;
}
if (Cartesian3.equalsEpsilon(nextPosition, groundPosition, CesiumMath.EPSILON10)) {
recomputeNormal = true;
} else {
s += ds;
if (vertexFormat.tangent) {
tangent = Cartesian3.normalize(Cartesian3.subtract(nextPosition, groundPosition, tangent), tangent);
}
if (vertexFormat.binormal) {
binormal = Cartesian3.normalize(Cartesian3.cross(normal, tangent, binormal), binormal);
}
}
if (vertexFormat.normal) {
normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;
normals[normalIndex++] = normal.x;
normals[normalIndex++] = normal.y;
normals[normalIndex++] = normal.z;
}
if (vertexFormat.tangent) {
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
tangents[tangentIndex++] = tangent.x;
tangents[tangentIndex++] = tangent.y;
tangents[tangentIndex++] = tangent.z;
}
if (vertexFormat.binormal) {
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
binormals[binormalIndex++] = binormal.x;
binormals[binormalIndex++] = binormal.y;
binormals[binormalIndex++] = binormal.z;
}
}
}
var attributes = new GeometryAttributes();
if (vertexFormat.position) {
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
}
if (vertexFormat.normal) {
attributes.normal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : normals
});
}
if (vertexFormat.tangent) {
attributes.tangent = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : tangents
});
}
if (vertexFormat.binormal) {
attributes.binormal = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : binormals
});
}
if (vertexFormat.st) {
attributes.st = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : textureCoordinates
});
}
// prepare the side walls, two triangles for each wall
//
// A (i+1) B (i+3) E
// +--------+-------+
// | / | /| triangles: A C B
// | / | / | B C D
// | / | / |
// | / | / |
// | / | / |
// | / | / |
// +--------+-------+
// C (i) D (i+2) F
//
var numVertices = size / 3;
size -= 6 * (numCorners + 1);
var indices = IndexDatatype.createTypedArray(numVertices, size);
var edgeIndex = 0;
for (i = 0; i < numVertices - 2; i += 2) {
var LL = i;
var LR = i + 2;
var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1);
var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2);
if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON10)) {
continue;
}
var UL = i + 1;
var UR = i + 3;
indices[edgeIndex++] = UL;
indices[edgeIndex++] = LL;
indices[edgeIndex++] = UR;
indices[edgeIndex++] = UR;
indices[edgeIndex++] = LL;
indices[edgeIndex++] = LR;
}
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : new BoundingSphere.fromVertices(positions)
});
};
return WallGeometry;
});
/*global define*/
define('Core/WallOutlineGeometry',[
'./BoundingSphere',
'./Cartesian3',
'./ComponentDatatype',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Ellipsoid',
'./Geometry',
'./GeometryAttribute',
'./GeometryAttributes',
'./IndexDatatype',
'./Math',
'./PrimitiveType',
'./WallGeometryLibrary'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defaultValue,
defined,
DeveloperError,
Ellipsoid,
Geometry,
GeometryAttribute,
GeometryAttributes,
IndexDatatype,
CesiumMath,
PrimitiveType,
WallGeometryLibrary) {
'use strict';
var scratchCartesian3Position1 = new Cartesian3();
var scratchCartesian3Position2 = new Cartesian3();
/**
* A description of a wall outline. A wall is defined by a series of points,
* which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
*
* @alias WallOutlineGeometry
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
* @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
* @param {Number[]} [options.maximumHeights] An array parallel to positions
that give the maximum height of the
* wall at positions
. If undefined, the height of each position in used.
* @param {Number[]} [options.minimumHeights] An array parallel to positions
that give the minimum height of the
* wall at positions
. If undefined, the height at each position is 0.0.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
*
* @exception {DeveloperError} positions length must be greater than or equal to 2.
* @exception {DeveloperError} positions and maximumHeights must have the same length.
* @exception {DeveloperError} positions and minimumHeights must have the same length.
*
* @see WallGeometry#createGeometry
* @see WallGeometry#fromConstantHeight
*
* @example
* // create a wall outline that spans from ground level to 10000 meters
* var wall = new Cesium.WallOutlineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArrayHeights([
* 19.0, 47.0, 10000.0,
* 19.0, 48.0, 10000.0,
* 20.0, 48.0, 10000.0,
* 20.0, 47.0, 10000.0,
* 19.0, 47.0, 10000.0
* ])
* });
* var geometry = Cesium.WallOutlineGeometry.createGeometry(wall);
*/
function WallOutlineGeometry(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var wallPositions = options.positions;
var maximumHeights = options.maximumHeights;
var minimumHeights = options.minimumHeights;
if (!defined(wallPositions)) {
throw new DeveloperError('options.positions is required.');
}
if (defined(maximumHeights) && maximumHeights.length !== wallPositions.length) {
throw new DeveloperError('options.positions and options.maximumHeights must have the same length.');
}
if (defined(minimumHeights) && minimumHeights.length !== wallPositions.length) {
throw new DeveloperError('options.positions and options.minimumHeights must have the same length.');
}
var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
this._positions = wallPositions;
this._minimumHeights = minimumHeights;
this._maximumHeights = maximumHeights;
this._granularity = granularity;
this._ellipsoid = Ellipsoid.clone(ellipsoid);
this._workerName = 'createWallOutlineGeometry';
var numComponents = 1 + wallPositions.length * Cartesian3.packedLength + 2;
if (defined(minimumHeights)) {
numComponents += minimumHeights.length;
}
if (defined(maximumHeights)) {
numComponents += maximumHeights.length;
}
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
this.packedLength = numComponents + Ellipsoid.packedLength + 1;
}
/**
* Stores the provided instance into the provided array.
*
* @param {WallOutlineGeometry} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
WallOutlineGeometry.pack = function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var positions = value._positions;
var length = positions.length;
array[startingIndex++] = length;
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
Cartesian3.pack(positions[i], array, startingIndex);
}
var minimumHeights = value._minimumHeights;
length = defined(minimumHeights) ? minimumHeights.length : 0;
array[startingIndex++] = length;
if (defined(minimumHeights)) {
for (i = 0; i < length; ++i) {
array[startingIndex++] = minimumHeights[i];
}
}
var maximumHeights = value._maximumHeights;
length = defined(maximumHeights) ? maximumHeights.length : 0;
array[startingIndex++] = length;
if (defined(maximumHeights)) {
for (i = 0; i < length; ++i) {
array[startingIndex++] = maximumHeights[i];
}
}
Ellipsoid.pack(value._ellipsoid, array, startingIndex);
startingIndex += Ellipsoid.packedLength;
array[startingIndex] = value._granularity;
return array;
};
var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
var scratchOptions = {
positions : undefined,
minimumHeights : undefined,
maximumHeights : undefined,
ellipsoid : scratchEllipsoid,
granularity : undefined
};
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {WallOutlineGeometry} [result] The object into which to store the result.
* @returns {WallOutlineGeometry} The modified result parameter or a new WallOutlineGeometry instance if one was not provided.
*/
WallOutlineGeometry.unpack = function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
var i;
var length = array[startingIndex++];
var positions = new Array(length);
for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
positions[i] = Cartesian3.unpack(array, startingIndex);
}
length = array[startingIndex++];
var minimumHeights;
if (length > 0) {
minimumHeights = new Array(length);
for (i = 0; i < length; ++i) {
minimumHeights[i] = array[startingIndex++];
}
}
length = array[startingIndex++];
var maximumHeights;
if (length > 0) {
maximumHeights = new Array(length);
for (i = 0; i < length; ++i) {
maximumHeights[i] = array[startingIndex++];
}
}
var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
startingIndex += Ellipsoid.packedLength;
var granularity = array[startingIndex];
if (!defined(result)) {
scratchOptions.positions = positions;
scratchOptions.minimumHeights = minimumHeights;
scratchOptions.maximumHeights = maximumHeights;
scratchOptions.granularity = granularity;
return new WallOutlineGeometry(scratchOptions);
}
result._positions = positions;
result._minimumHeights = minimumHeights;
result._maximumHeights = maximumHeights;
result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
result._granularity = granularity;
return result;
};
/**
* A description of a walloutline. A wall is defined by a series of points,
* which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
*
* @param {Object} options Object with the following properties:
* @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
* @param {Number} [options.maximumHeight] A constant that defines the maximum height of the
* wall at positions
. If undefined, the height of each position in used.
* @param {Number} [options.minimumHeight] A constant that defines the minimum height of the
* wall at positions
. If undefined, the height at each position is 0.0.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
* @returns {WallOutlineGeometry}
*
*
* @example
* // create a wall that spans from 10000 meters to 20000 meters
* var wall = Cesium.WallOutlineGeometry.fromConstantHeights({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 19.0, 47.0,
* 19.0, 48.0,
* 20.0, 48.0,
* 20.0, 47.0,
* 19.0, 47.0,
* ]),
* minimumHeight : 20000.0,
* maximumHeight : 10000.0
* });
* var geometry = Cesium.WallOutlineGeometry.createGeometry(wall);
*
* @see WallOutlineGeometry#createGeometry
*/
WallOutlineGeometry.fromConstantHeights = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var positions = options.positions;
if (!defined(positions)) {
throw new DeveloperError('options.positions is required.');
}
var minHeights;
var maxHeights;
var min = options.minimumHeight;
var max = options.maximumHeight;
var doMin = defined(min);
var doMax = defined(max);
if (doMin || doMax) {
var length = positions.length;
minHeights = (doMin) ? new Array(length) : undefined;
maxHeights = (doMax) ? new Array(length) : undefined;
for (var i = 0; i < length; ++i) {
if (doMin) {
minHeights[i] = min;
}
if (doMax) {
maxHeights[i] = max;
}
}
}
var newOptions = {
positions : positions,
maximumHeights : maxHeights,
minimumHeights : minHeights,
ellipsoid : options.ellipsoid
};
return new WallOutlineGeometry(newOptions);
};
/**
* Computes the geometric representation of a wall outline, including its vertices, indices, and a bounding sphere.
*
* @param {WallOutlineGeometry} wallGeometry A description of the wall outline.
* @returns {Geometry|undefined} The computed vertices and indices.
*/
WallOutlineGeometry.createGeometry = function(wallGeometry) {
var wallPositions = wallGeometry._positions;
var minimumHeights = wallGeometry._minimumHeights;
var maximumHeights = wallGeometry._maximumHeights;
var granularity = wallGeometry._granularity;
var ellipsoid = wallGeometry._ellipsoid;
var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, false);
if (!defined(pos)) {
return;
}
var bottomPositions = pos.bottomPositions;
var topPositions = pos.topPositions;
var length = topPositions.length;
var size = length * 2;
var positions = new Float64Array(size);
var positionIndex = 0;
// add lower and upper points one after the other, lower
// points being even and upper points being odd
length /= 3;
var i;
for (i = 0; i < length; ++i) {
var i3 = i * 3;
var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1);
var bottomPosition = Cartesian3.fromArray(bottomPositions, i3, scratchCartesian3Position2);
// insert the lower point
positions[positionIndex++] = bottomPosition.x;
positions[positionIndex++] = bottomPosition.y;
positions[positionIndex++] = bottomPosition.z;
// insert the upper point
positions[positionIndex++] = topPosition.x;
positions[positionIndex++] = topPosition.y;
positions[positionIndex++] = topPosition.z;
}
var attributes = new GeometryAttributes({
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
})
});
var numVertices = size / 3;
size = 2 * numVertices - 4 + numVertices;
var indices = IndexDatatype.createTypedArray(numVertices, size);
var edgeIndex = 0;
for (i = 0; i < numVertices - 2; i += 2) {
var LL = i;
var LR = i + 2;
var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1);
var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2);
if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON10)) {
continue;
}
var UL = i + 1;
var UR = i + 3;
indices[edgeIndex++] = UL;
indices[edgeIndex++] = LL;
indices[edgeIndex++] = UL;
indices[edgeIndex++] = UR;
indices[edgeIndex++] = LL;
indices[edgeIndex++] = LR;
}
indices[edgeIndex++] = numVertices - 2;
indices[edgeIndex++] = numVertices - 1;
return new Geometry({
attributes : attributes,
indices : indices,
primitiveType : PrimitiveType.LINES,
boundingSphere : new BoundingSphere.fromVertices(positions)
});
};
return WallOutlineGeometry;
});
/*global define*/
define('Core/WebMercatorTilingScheme',[
'./Cartesian2',
'./defaultValue',
'./defined',
'./defineProperties',
'./Ellipsoid',
'./Rectangle',
'./WebMercatorProjection'
], function(
Cartesian2,
defaultValue,
defined,
defineProperties,
Ellipsoid,
Rectangle,
WebMercatorProjection) {
'use strict';
/**
* A tiling scheme for geometry referenced to a {@link WebMercatorProjection}, EPSG:3857. This is
* the tiling scheme used by Google Maps, Microsoft Bing Maps, and most of ESRI ArcGIS Online.
*
* @alias WebMercatorTilingScheme
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
* the WGS84 ellipsoid.
* @param {Number} [options.numberOfLevelZeroTilesX=1] The number of tiles in the X direction at level zero of
* the tile tree.
* @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
* the tile tree.
* @param {Cartesian2} [options.rectangleSouthwestInMeters] The southwest corner of the rectangle covered by the
* tiling scheme, in meters. If this parameter or rectangleNortheastInMeters is not specified, the entire
* globe is covered in the longitude direction and an equal distance is covered in the latitude
* direction, resulting in a square projection.
* @param {Cartesian2} [options.rectangleNortheastInMeters] The northeast corner of the rectangle covered by the
* tiling scheme, in meters. If this parameter or rectangleSouthwestInMeters is not specified, the entire
* globe is covered in the longitude direction and an equal distance is covered in the latitude
* direction, resulting in a square projection.
*/
function WebMercatorTilingScheme(options) {
options = defaultValue(options, {});
this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
this._numberOfLevelZeroTilesX = defaultValue(options.numberOfLevelZeroTilesX, 1);
this._numberOfLevelZeroTilesY = defaultValue(options.numberOfLevelZeroTilesY, 1);
this._projection = new WebMercatorProjection(this._ellipsoid);
if (defined(options.rectangleSouthwestInMeters) &&
defined(options.rectangleNortheastInMeters)) {
this._rectangleSouthwestInMeters = options.rectangleSouthwestInMeters;
this._rectangleNortheastInMeters = options.rectangleNortheastInMeters;
} else {
var semimajorAxisTimesPi = this._ellipsoid.maximumRadius * Math.PI;
this._rectangleSouthwestInMeters = new Cartesian2(-semimajorAxisTimesPi, -semimajorAxisTimesPi);
this._rectangleNortheastInMeters = new Cartesian2(semimajorAxisTimesPi, semimajorAxisTimesPi);
}
var southwest = this._projection.unproject(this._rectangleSouthwestInMeters);
var northeast = this._projection.unproject(this._rectangleNortheastInMeters);
this._rectangle = new Rectangle(southwest.longitude, southwest.latitude,
northeast.longitude, northeast.latitude);
}
defineProperties(WebMercatorTilingScheme.prototype, {
/**
* Gets the ellipsoid that is tiled by this tiling scheme.
* @memberof WebMercatorTilingScheme.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Gets the rectangle, in radians, covered by this tiling scheme.
* @memberof WebMercatorTilingScheme.prototype
* @type {Rectangle}
*/
rectangle : {
get : function() {
return this._rectangle;
}
},
/**
* Gets the map projection used by this tiling scheme.
* @memberof WebMercatorTilingScheme.prototype
* @type {MapProjection}
*/
projection : {
get : function() {
return this._projection;
}
}
});
/**
* Gets the total number of tiles in the X direction at a specified level-of-detail.
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the X direction at the given level.
*/
WebMercatorTilingScheme.prototype.getNumberOfXTilesAtLevel = function(level) {
return this._numberOfLevelZeroTilesX << level;
};
/**
* Gets the total number of tiles in the Y direction at a specified level-of-detail.
*
* @param {Number} level The level-of-detail.
* @returns {Number} The number of tiles in the Y direction at the given level.
*/
WebMercatorTilingScheme.prototype.getNumberOfYTilesAtLevel = function(level) {
return this._numberOfLevelZeroTilesY << level;
};
/**
* Transforms an rectangle specified in geodetic radians to the native coordinate system
* of this tiling scheme.
*
* @param {Rectangle} rectangle The rectangle to transform.
* @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
* is undefined.
*/
WebMercatorTilingScheme.prototype.rectangleToNativeRectangle = function(rectangle, result) {
var projection = this._projection;
var southwest = projection.project(Rectangle.southwest(rectangle));
var northeast = projection.project(Rectangle.northeast(rectangle));
if (!defined(result)) {
return new Rectangle(southwest.x, southwest.y, northeast.x, northeast.y);
}
result.west = southwest.x;
result.south = southwest.y;
result.east = northeast.x;
result.north = northeast.y;
return result;
};
/**
* Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
* of the tiling scheme.
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
WebMercatorTilingScheme.prototype.tileXYToNativeRectangle = function(x, y, level, result) {
var xTiles = this.getNumberOfXTilesAtLevel(level);
var yTiles = this.getNumberOfYTilesAtLevel(level);
var xTileWidth = (this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x) / xTiles;
var west = this._rectangleSouthwestInMeters.x + x * xTileWidth;
var east = this._rectangleSouthwestInMeters.x + (x + 1) * xTileWidth;
var yTileHeight = (this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y) / yTiles;
var north = this._rectangleNortheastInMeters.y - y * yTileHeight;
var south = this._rectangleNortheastInMeters.y - (y + 1) * yTileHeight;
if (!defined(result)) {
return new Rectangle(west, south, east, north);
}
result.west = west;
result.south = south;
result.east = east;
result.north = north;
return result;
};
/**
* Converts tile x, y coordinates and level to a cartographic rectangle in radians.
*
* @param {Number} x The integer x coordinate of the tile.
* @param {Number} y The integer y coordinate of the tile.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Rectangle} The specified 'result', or a new object containing the rectangle
* if 'result' is undefined.
*/
WebMercatorTilingScheme.prototype.tileXYToRectangle = function(x, y, level, result) {
var nativeRectangle = this.tileXYToNativeRectangle(x, y, level, result);
var projection = this._projection;
var southwest = projection.unproject(new Cartesian2(nativeRectangle.west, nativeRectangle.south));
var northeast = projection.unproject(new Cartesian2(nativeRectangle.east, nativeRectangle.north));
nativeRectangle.west = southwest.longitude;
nativeRectangle.south = southwest.latitude;
nativeRectangle.east = northeast.longitude;
nativeRectangle.north = northeast.latitude;
return nativeRectangle;
};
/**
* Calculates the tile x, y coordinates of the tile containing
* a given cartographic position.
*
* @param {Cartographic} position The position.
* @param {Number} level The tile level-of-detail. Zero is the least detailed.
* @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
* should be created.
* @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
* if 'result' is undefined.
*/
WebMercatorTilingScheme.prototype.positionToTileXY = function(position, level, result) {
var rectangle = this._rectangle;
if (!Rectangle.contains(rectangle, position)) {
// outside the bounds of the tiling scheme
return undefined;
}
var xTiles = this.getNumberOfXTilesAtLevel(level);
var yTiles = this.getNumberOfYTilesAtLevel(level);
var overallWidth = this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x;
var xTileWidth = overallWidth / xTiles;
var overallHeight = this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y;
var yTileHeight = overallHeight / yTiles;
var projection = this._projection;
var webMercatorPosition = projection.project(position);
var distanceFromWest = webMercatorPosition.x - this._rectangleSouthwestInMeters.x;
var distanceFromNorth = this._rectangleNortheastInMeters.y - webMercatorPosition.y;
var xTileCoordinate = distanceFromWest / xTileWidth | 0;
if (xTileCoordinate >= xTiles) {
xTileCoordinate = xTiles - 1;
}
var yTileCoordinate = distanceFromNorth / yTileHeight | 0;
if (yTileCoordinate >= yTiles) {
yTileCoordinate = yTiles - 1;
}
if (!defined(result)) {
return new Cartesian2(xTileCoordinate, yTileCoordinate);
}
result.x = xTileCoordinate;
result.y = yTileCoordinate;
return result;
};
return WebMercatorTilingScheme;
});
/*global define*/
define('Core/wrapFunction',[
'./DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* Wraps a function on the provided objects with another function called in the
* object's context so that the new function is always called immediately
* before the old one.
*
* @private
*/
function wrapFunction(obj, oldFunction, newFunction) {
if (typeof oldFunction !== 'function') {
throw new DeveloperError("oldFunction is required to be a function.");
}
if (typeof newFunction !== 'function') {
throw new DeveloperError("oldFunction is required to be a function.");
}
return function() {
newFunction.apply(obj, arguments);
oldFunction.apply(obj, arguments);
};
}
return wrapFunction;
});
/*global define*/
define('DataSources/ConstantProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/Event'
], function(
defined,
defineProperties,
Event) {
'use strict';
/**
* A {@link Property} whose value does not change with respect to simulation time.
*
* @alias ConstantProperty
* @constructor
*
* @param {Object} [value] The property value.
*
* @see ConstantPositionProperty
*
* @exception {DeveloperError} value.clone is a required function.
* @exception {DeveloperError} value.equals is a required function.
*/
function ConstantProperty(value) {
this._value = undefined;
this._hasClone = false;
this._hasEquals = false;
this._definitionChanged = new Event();
this.setValue(value);
}
defineProperties(ConstantProperty.prototype, {
/**
* Gets a value indicating if this property is constant.
* This property always returns true
.
* @memberof ConstantProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
value : true
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value.
* @memberof ConstantProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
}
});
/**
* Gets the value of the property.
*
* @param {JulianDate} [time] The time for which to retrieve the value. This parameter is unused since the value does not change with respect to time.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ConstantProperty.prototype.getValue = function(time, result) {
return this._hasClone ? this._value.clone(result) : this._value;
};
/**
* Sets the value of the property.
*
* @param {Object} value The property value.
*
* @exception {DeveloperError} value.clone is a required function.
* @exception {DeveloperError} value.equals is a required function.
*/
ConstantProperty.prototype.setValue = function(value) {
var oldValue = this._value;
if (oldValue !== value) {
var isDefined = defined(value);
var hasClone = isDefined && typeof value.clone === 'function';
var hasEquals = isDefined && typeof value.equals === 'function';
this._hasClone = hasClone;
this._hasEquals = hasEquals;
var changed = !hasEquals || !value.equals(oldValue);
if (changed) {
this._value = !hasClone ? value : value.clone();
this._definitionChanged.raiseEvent(this);
}
}
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
ConstantProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof ConstantProperty && //
((!this._hasEquals && (this._value === other._value)) || //
(this._hasEquals && this._value.equals(other._value))));
};
return ConstantProperty;
});
/*global define*/
define('DataSources/createPropertyDescriptor',[
'../Core/defaultValue',
'../Core/defined',
'./ConstantProperty'
], function(
defaultValue,
defined,
ConstantProperty) {
'use strict';
function createProperty(name, privateName, subscriptionName, configurable, createPropertyCallback) {
return {
configurable : configurable,
get : function() {
return this[privateName];
},
set : function(value) {
var oldValue = this[privateName];
var subscription = this[subscriptionName];
if (defined(subscription)) {
subscription();
this[subscriptionName] = undefined;
}
var hasValue = defined(value);
if (hasValue && !defined(value.getValue) && defined(createPropertyCallback)) {
value = createPropertyCallback(value);
}
if (oldValue !== value) {
this[privateName] = value;
this._definitionChanged.raiseEvent(this, name, value, oldValue);
}
if (defined(value) && defined(value.definitionChanged)) {
this[subscriptionName] = value.definitionChanged.addEventListener(function() {
this._definitionChanged.raiseEvent(this, name, value, value);
}, this);
}
}
};
}
function createConstantProperty(value) {
return new ConstantProperty(value);
}
/**
* Used to consistently define all DataSources graphics objects.
* This is broken into two functions because the Chrome profiler does a better
* job of optimizing lookups if it notices that the string is constant throughout the function.
* @private
*/
function createPropertyDescriptor(name, configurable, createPropertyCallback) {
//Safari 8.0.3 has a JavaScript bug that causes it to confuse two variables and treat them as the same.
//The two extra toString calls work around the issue.
return createProperty(name, '_' + name.toString(), '_' + name.toString() + 'Subscription', defaultValue(configurable, false), defaultValue(createPropertyCallback, createConstantProperty));
}
return createPropertyDescriptor;
});
/*global define*/
define('DataSources/BillboardGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createPropertyDescriptor) {
'use strict';
/**
* Describes a two dimensional icon located at the position of the containing {@link Entity}.
*
*
*
* Example billboards
*
*
*
* @alias BillboardGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.image] A Property specifying the Image, URI, or Canvas to use for the billboard.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the billboard.
* @param {Property} [options.scale=1.0] A numeric Property specifying the scale to apply to the image size.
* @param {Property} [options.horizontalOrigin=HorizontalOrigin.CENTER] A Property specifying the {@link HorizontalOrigin}.
* @param {Property} [options.verticalOrigin=VerticalOrigin.CENTER] A Property specifying the {@link VerticalOrigin}.
* @param {Property} [options.eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the eye offset.
* @param {Property} [options.pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Property specifying the pixel offset.
* @param {Property} [options.rotation=0] A numeric Property specifying the rotation about the alignedAxis.
* @param {Property} [options.alignedAxis=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the unit vector axis of rotation.
* @param {Property} [options.width] A numeric Property specifying the width of the billboard in pixels, overriding the native size.
* @param {Property} [options.height] A numeric Property specifying the height of the billboard in pixels, overriding the native size.
* @param {Property} [options.color=Color.WHITE] A Property specifying the tint {@link Color} of the image.
* @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to scale the point based on distance from the camera.
* @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera.
* @param {Property} [options.pixelOffsetScaleByDistance] A {@link NearFarScalar} Property used to set pixelOffset based on distance from the camera.
* @param {Property} [options.imageSubRegion] A Property specifying a {@link BoundingRectangle} that defines a sub-region of the image to use for the billboard, rather than the entire image, measured in pixels from the bottom-left.
* @param {Property} [options.sizeInMeters] A boolean Property specifying whether this billboard's size should be measured in meters.
* @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this billboard will be displayed.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
*/
function BillboardGraphics(options) {
this._image = undefined;
this._imageSubscription = undefined;
this._imageSubRegion = undefined;
this._imageSubRegionSubscription = undefined;
this._width = undefined;
this._widthSubscription = undefined;
this._height = undefined;
this._heightSubscription = undefined;
this._scale = undefined;
this._scaleSubscription = undefined;
this._rotation = undefined;
this._rotationSubscription = undefined;
this._alignedAxis = undefined;
this._alignedAxisSubscription = undefined;
this._horizontalOrigin = undefined;
this._horizontalOriginSubscription = undefined;
this._verticalOrigin = undefined;
this._verticalOriginSubscription = undefined;
this._color = undefined;
this._colorSubscription = undefined;
this._eyeOffset = undefined;
this._eyeOffsetSubscription = undefined;
this._heightReference = undefined;
this._heightReferenceSubscription = undefined;
this._pixelOffset = undefined;
this._pixelOffsetSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._scaleByDistance = undefined;
this._scaleByDistanceSubscription = undefined;
this._translucencyByDistance = undefined;
this._translucencyByDistanceSubscription = undefined;
this._pixelOffsetScaleByDistance = undefined;
this._pixelOffsetScaleByDistanceSubscription = undefined;
this._sizeInMeters = undefined;
this._sizeInMetersSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(BillboardGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof BillboardGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the Image, URI, or Canvas to use for the billboard.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
image : createPropertyDescriptor('image'),
/**
* Gets or sets the Property specifying a {@link BoundingRectangle} that defines a
* sub-region of the image
to use for the billboard, rather than the entire image,
* measured in pixels from the bottom-left.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
imageSubRegion : createPropertyDescriptor('imageSubRegion'),
/**
* Gets or sets the numeric Property specifying the uniform scale to apply to the image.
* A scale greater than 1.0
enlarges the billboard while a scale less than 1.0
shrinks it.
*
*
*
* From left to right in the above image, the scales are
0.5
,
1.0
, and
2.0
.
*
*
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default 1.0
*/
scale : createPropertyDescriptor('scale'),
/**
* Gets or sets the numeric Property specifying the rotation of the image
* counter clockwise from the alignedAxis
.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default 0
*/
rotation : createPropertyDescriptor('rotation'),
/**
* Gets or sets the {@link Cartesian3} Property specifying the unit vector axis of rotation
* in the fixed frame. When set to Cartesian3.ZERO the rotation is from the top of the screen.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default Cartesian3.ZERO
*/
alignedAxis : createPropertyDescriptor('alignedAxis'),
/**
* Gets or sets the Property specifying the {@link HorizontalOrigin}.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default HorizontalOrigin.CENTER
*/
horizontalOrigin : createPropertyDescriptor('horizontalOrigin'),
/**
* Gets or sets the Property specifying the {@link VerticalOrigin}.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default VerticalOrigin.CENTER
*/
verticalOrigin : createPropertyDescriptor('verticalOrigin'),
/**
* Gets or sets the Property specifying the {@link Color} that is multiplied with the image
.
* This has two common use cases. First, the same white texture may be used by many different billboards,
* each with a different color, to create colored billboards. Second, the color's alpha component can be
* used to make the billboard translucent as shown below. An alpha of 0.0
makes the billboard
* transparent, and 1.0
makes the billboard opaque.
*
*
*
* default
* alpha : 0.5
*
*
*
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the {@link Cartesian3} Property specifying the billboard's offset in eye coordinates.
* Eye coordinates is a left-handed coordinate system, where x
points towards the viewer's
* right, y
points up, and z
points into the screen.
*
* An eye offset is commonly used to arrange multiple billboards or objects at the same position, e.g., to
* arrange a billboard above its corresponding 3D model.
*
* Below, the billboard is positioned at the center of the Earth but an eye offset makes it always
* appear on top of the Earth regardless of the viewer's or Earth's orientation.
*
*
*
*
*
*
*
b.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);
*
*
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default Cartesian3.ZERO
*/
eyeOffset : createPropertyDescriptor('eyeOffset'),
/**
* Gets or sets the Property specifying the {@link HeightReference}.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default HeightReference.NONE
*/
heightReference : createPropertyDescriptor('heightReference'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the billboard's pixel offset in screen space
* from the origin of this billboard. This is commonly used to align multiple billboards and labels at
* the same position, e.g., an image and text. The screen space origin is the top, left corner of the
* canvas; x
increases from left to right, and y
increases from top to bottom.
*
*
*
* default
* b.pixeloffset = new Cartesian2(50, 25);
*
* The billboard's origin is indicated by the yellow point.
*
*
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default Cartesian2.ZERO
*/
pixelOffset : createPropertyDescriptor('pixelOffset'),
/**
* Gets or sets the boolean Property specifying the visibility of the billboard.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the numeric Property specifying the billboard's width in pixels.
* When undefined, the native width is used.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
width : createPropertyDescriptor('width'),
/**
* Gets or sets the numeric Property specifying the height of the billboard in pixels.
* When undefined, the native height is used.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
height : createPropertyDescriptor('height'),
/**
* Gets or sets {@link NearFarScalar} Property specifying the scale of the billboard based on the distance from the camera.
* A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the billboard's scale remains clamped to the nearest bound.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
scaleByDistance : createPropertyDescriptor('scaleByDistance'),
/**
* Gets or sets {@link NearFarScalar} Property specifying the translucency of the billboard based on the distance from the camera.
* A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the billboard's translucency remains clamped to the nearest bound.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
translucencyByDistance : createPropertyDescriptor('translucencyByDistance'),
/**
* Gets or sets {@link NearFarScalar} Property specifying the pixel offset of the billboard based on the distance from the camera.
* A billboard's pixel offset will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the billboard's pixel offset remains clamped to the nearest bound.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance'),
/**
* Gets or sets the boolean Property specifying if this billboard's size will be measured in meters.
* @memberof BillboardGraphics.prototype
* @type {Property}
* @default false
*/
sizeInMeters : createPropertyDescriptor('sizeInMeters'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this billboard will be displayed.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {BillboardGraphics} [result] The object onto which to store the result.
* @returns {BillboardGraphics} The modified result parameter or a new instance if one was not provided.
*/
BillboardGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new BillboardGraphics(this);
}
result.color = this._color;
result.eyeOffset = this._eyeOffset;
result.heightReference = this._heightReference;
result.horizontalOrigin = this._horizontalOrigin;
result.image = this._image;
result.imageSubRegion = this._imageSubRegion;
result.pixelOffset = this._pixelOffset;
result.scale = this._scale;
result.rotation = this._rotation;
result.alignedAxis = this._alignedAxis;
result.show = this._show;
result.verticalOrigin = this._verticalOrigin;
result.width = this._width;
result.height = this._height;
result.scaleByDistance = this._scaleByDistance;
result.translucencyByDistance = this._translucencyByDistance;
result.pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
result.sizeInMeters = this._sizeInMeters;
result.distanceDisplayCondition = this._distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {BillboardGraphics} source The object to be merged into this object.
*/
BillboardGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.color = defaultValue(this._color, source.color);
this.eyeOffset = defaultValue(this._eyeOffset, source.eyeOffset);
this.heightReference = defaultValue(this._heightReference, source.heightReference);
this.horizontalOrigin = defaultValue(this._horizontalOrigin, source.horizontalOrigin);
this.image = defaultValue(this._image, source.image);
this.imageSubRegion = defaultValue(this._imageSubRegion, source.imageSubRegion);
this.pixelOffset = defaultValue(this._pixelOffset, source.pixelOffset);
this.scale = defaultValue(this._scale, source.scale);
this.rotation = defaultValue(this._rotation, source.rotation);
this.alignedAxis = defaultValue(this._alignedAxis, source.alignedAxis);
this.show = defaultValue(this._show, source.show);
this.verticalOrigin = defaultValue(this._verticalOrigin, source.verticalOrigin);
this.width = defaultValue(this._width, source.width);
this.height = defaultValue(this._height, source.height);
this.scaleByDistance = defaultValue(this._scaleByDistance, source.scaleByDistance);
this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance);
this.pixelOffsetScaleByDistance = defaultValue(this._pixelOffsetScaleByDistance, source.pixelOffsetScaleByDistance);
this.sizeInMeters = defaultValue(this._sizeInMeters, source.sizeInMeters);
this.distanceDisplayCondition = defaultValue(this._distanceDisplayCondition, source.distanceDisplayCondition);
};
return BillboardGraphics;
});
/*global define*/
define('Scene/HeightReference',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Represents the position relative to the terrain.
*
* @exports HeightReference
*/
var HeightReference = {
/**
* The position is absolute.
* @type {Number}
* @constant
*/
NONE : 0,
/**
* The position is clamped to the terrain.
* @type {Number}
* @constant
*/
CLAMP_TO_GROUND : 1,
/**
* The position height is the height above the terrain.
* @type {Number}
* @constant
*/
RELATIVE_TO_GROUND : 2
};
return freezeObject(HeightReference);
});
/*global define*/
define('Scene/HorizontalOrigin',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* The horizontal location of an origin relative to an object, e.g., a {@link Billboard}
* or {@link Label}. For example, setting the horizontal origin to LEFT
* or RIGHT
will display a billboard to the left or right (in screen space)
* of the anchor position.
*
*
*
*
*
* @exports HorizontalOrigin
*
* @see Billboard#horizontalOrigin
* @see Label#horizontalOrigin
*/
var HorizontalOrigin = {
/**
* The origin is at the horizontal center of the object.
*
* @type {Number}
* @constant
*/
CENTER : 0,
/**
* The origin is on the left side of the object.
*
* @type {Number}
* @constant
*/
LEFT : 1,
/**
* The origin is on the right side of the object.
*
* @type {Number}
* @constant
*/
RIGHT : -1
};
return freezeObject(HorizontalOrigin);
});
/*global define*/
define('Scene/VerticalOrigin',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* The vertical location of an origin relative to an object, e.g., a {@link Billboard}
* or {@link Label}. For example, setting the vertical origin to TOP
* or BOTTOM
will display a billboard above or below (in screen space)
* the anchor position.
*
*
*
*
*
* @exports VerticalOrigin
*
* @see Billboard#verticalOrigin
* @see Label#verticalOrigin
*/
var VerticalOrigin = {
/**
* The origin is at the vertical center between BASELINE
and TOP
.
*
* @type {Number}
* @constant
*/
CENTER : 0,
/**
* The origin is at the bottom of the object.
*
* @type {Number}
* @constant
*/
BOTTOM : 1,
/**
* If the object contains text, the origin is at the baseline of the text, else the origin is at the bottom of the object.
*
* @type {Number}
* @constant
*/
BASELINE : 2,
/**
* The origin is at the top of the object.
*
* @type {Number}
* @constant
*/
TOP : -1
};
return freezeObject(VerticalOrigin);
});
/*global define*/
define('DataSources/BoundingSphereState',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* The state of a BoundingSphere computation being performed by a {@link Visualizer}.
* @exports BoundingSphereState
* @private
*/
var BoundingSphereState = {
/**
* The BoundingSphere has been computed.
* @type BoundingSphereState
* @constant
*/
DONE : 0,
/**
* The BoundingSphere is still being computed.
* @type BoundingSphereState
* @constant
*/
PENDING : 1,
/**
* The BoundingSphere does not exist.
* @type BoundingSphereState
* @constant
*/
FAILED : 2
};
return freezeObject(BoundingSphereState);
});
/*global define*/
define('DataSources/Property',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* The interface for all properties, which represent a value that can optionally vary over time.
* This type defines an interface and cannot be instantiated directly.
*
* @alias Property
* @constructor
*
* @see CompositeProperty
* @see ConstantProperty
* @see SampledProperty
* @see TimeIntervalCollectionProperty
* @see MaterialProperty
* @see PositionProperty
* @see ReferenceProperty
*/
function Property() {
DeveloperError.throwInstantiationError();
}
defineProperties(Property.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof Property.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof Property.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Gets the value of the property at the provided time.
* @function
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
Property.prototype.getValue = DeveloperError.throwInstantiationError;
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
* @function
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
Property.prototype.equals = DeveloperError.throwInstantiationError;
/**
* @private
*/
Property.equals = function(left, right) {
return left === right || (defined(left) && left.equals(right));
};
/**
* @private
*/
Property.arrayEquals = function(left, right) {
if (left === right) {
return true;
}
if ((!defined(left) || !defined(right)) || (left.length !== right.length)) {
return false;
}
var length = left.length;
for (var i = 0; i < length; i++) {
if (!Property.equals(left[i], right[i])) {
return false;
}
}
return true;
};
/**
* @private
*/
Property.isConstant = function(property) {
return !defined(property) || property.isConstant;
};
/**
* @private
*/
Property.getValueOrUndefined = function(property, time, result) {
return defined(property) ? property.getValue(time, result) : undefined;
};
/**
* @private
*/
Property.getValueOrDefault = function(property, time, valueDefault, result) {
return defined(property) ? defaultValue(property.getValue(time, result), valueDefault) : valueDefault;
};
/**
* @private
*/
Property.getValueOrClonedDefault = function(property, time, valueDefault, result) {
var value;
if (defined(property)) {
value = property.getValue(time, result);
}
if (!defined(value)) {
value = valueDefault.clone(value);
}
return value;
};
return Property;
});
/*global define*/
define('DataSources/BillboardVisualizer',[
'../Core/AssociativeArray',
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/NearFarScalar',
'../Scene/HeightReference',
'../Scene/HorizontalOrigin',
'../Scene/VerticalOrigin',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
BoundingRectangle,
Cartesian2,
Cartesian3,
Color,
defined,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
NearFarScalar,
HeightReference,
HorizontalOrigin,
VerticalOrigin,
BoundingSphereState,
Property) {
'use strict';
var defaultColor = Color.WHITE;
var defaultEyeOffset = Cartesian3.ZERO;
var defaultHeightReference = HeightReference.NONE;
var defaultPixelOffset = Cartesian2.ZERO;
var defaultScale = 1.0;
var defaultRotation = 0.0;
var defaultAlignedAxis = Cartesian3.ZERO;
var defaultHorizontalOrigin = HorizontalOrigin.CENTER;
var defaultVerticalOrigin = VerticalOrigin.CENTER;
var defaultSizeInMeters = false;
var position = new Cartesian3();
var color = new Color();
var eyeOffset = new Cartesian3();
var pixelOffset = new Cartesian2();
var scaleByDistance = new NearFarScalar();
var translucencyByDistance = new NearFarScalar();
var pixelOffsetScaleByDistance = new NearFarScalar();
var boundingRectangle = new BoundingRectangle();
var distanceDisplayCondition = new DistanceDisplayCondition();
function EntityData(entity) {
this.entity = entity;
this.billboard = undefined;
this.textureValue = undefined;
}
/**
* A {@link Visualizer} which maps {@link Entity#billboard} to a {@link Billboard}.
* @alias BillboardVisualizer
* @constructor
*
* @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
* @param {EntityCollection} entityCollection The entityCollection to visualize.
*/
function BillboardVisualizer(entityCluster, entityCollection) {
if (!defined(entityCluster)) {
throw new DeveloperError('entityCluster is required.');
}
if (!defined(entityCollection)) {
throw new DeveloperError('entityCollection is required.');
}
entityCollection.collectionChanged.addEventListener(BillboardVisualizer.prototype._onCollectionChanged, this);
this._cluster = entityCluster;
this._entityCollection = entityCollection;
this._items = new AssociativeArray();
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
}
/**
* Updates the primitives created by this visualizer to match their
* Entity counterpart at the given time.
*
* @param {JulianDate} time The time to update to.
* @returns {Boolean} This function always returns true.
*/
BillboardVisualizer.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var items = this._items.values;
var cluster = this._cluster;
for (var i = 0, len = items.length; i < len; i++) {
var item = items[i];
var entity = item.entity;
var billboardGraphics = entity._billboard;
var textureValue;
var billboard = item.billboard;
var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(billboardGraphics._show, time, true);
if (show) {
position = Property.getValueOrUndefined(entity._position, time, position);
textureValue = Property.getValueOrUndefined(billboardGraphics._image, time);
show = defined(position) && defined(textureValue);
}
if (!show) {
//don't bother creating or updating anything else
returnPrimitive(item, entity, cluster);
continue;
}
if (!Property.isConstant(entity._position)) {
cluster._clusterDirty = true;
}
if (!defined(billboard)) {
billboard = cluster.getBillboard(entity);
billboard.id = entity;
billboard.image = undefined;
item.billboard = billboard;
}
billboard.show = show;
if (!defined(billboard.image) || item.textureValue !== textureValue) {
billboard.image = textureValue;
item.textureValue = textureValue;
}
billboard.position = position;
billboard.color = Property.getValueOrDefault(billboardGraphics._color, time, defaultColor, color);
billboard.eyeOffset = Property.getValueOrDefault(billboardGraphics._eyeOffset, time, defaultEyeOffset, eyeOffset);
billboard.heightReference = Property.getValueOrDefault(billboardGraphics._heightReference, time, defaultHeightReference);
billboard.pixelOffset = Property.getValueOrDefault(billboardGraphics._pixelOffset, time, defaultPixelOffset, pixelOffset);
billboard.scale = Property.getValueOrDefault(billboardGraphics._scale, time, defaultScale);
billboard.rotation = Property.getValueOrDefault(billboardGraphics._rotation, time, defaultRotation);
billboard.alignedAxis = Property.getValueOrDefault(billboardGraphics._alignedAxis, time, defaultAlignedAxis);
billboard.horizontalOrigin = Property.getValueOrDefault(billboardGraphics._horizontalOrigin, time, defaultHorizontalOrigin);
billboard.verticalOrigin = Property.getValueOrDefault(billboardGraphics._verticalOrigin, time, defaultVerticalOrigin);
billboard.width = Property.getValueOrUndefined(billboardGraphics._width, time);
billboard.height = Property.getValueOrUndefined(billboardGraphics._height, time);
billboard.scaleByDistance = Property.getValueOrUndefined(billboardGraphics._scaleByDistance, time, scaleByDistance);
billboard.translucencyByDistance = Property.getValueOrUndefined(billboardGraphics._translucencyByDistance, time, translucencyByDistance);
billboard.pixelOffsetScaleByDistance = Property.getValueOrUndefined(billboardGraphics._pixelOffsetScaleByDistance, time, pixelOffsetScaleByDistance);
billboard.sizeInMeters = Property.getValueOrDefault(billboardGraphics._sizeInMeters, defaultSizeInMeters);
billboard.distanceDisplayCondition = Property.getValueOrUndefined(billboardGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
var subRegion = Property.getValueOrUndefined(billboardGraphics._imageSubRegion, time, boundingRectangle);
if (defined(subRegion)) {
billboard.setImageSubRegion(billboard._imageId, subRegion);
}
}
return true;
};
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
BillboardVisualizer.prototype.getBoundingSphere = function(entity, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var item = this._items.get(entity.id);
if (!defined(item) || !defined(item.billboard)) {
return BoundingSphereState.FAILED;
}
var billboard = item.billboard;
if (billboard.heightReference === HeightReference.NONE) {
result.center = Cartesian3.clone(billboard.position, result.center);
} else {
if (!defined(billboard._clampedPosition)) {
return BoundingSphereState.PENDING;
}
result.center = Cartesian3.clone(billboard._clampedPosition, result.center);
}
result.radius = 0;
return BoundingSphereState.DONE;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
BillboardVisualizer.prototype.isDestroyed = function() {
return false;
};
/**
* Removes and destroys all primitives created by this instance.
*/
BillboardVisualizer.prototype.destroy = function() {
this._entityCollection.collectionChanged.removeEventListener(BillboardVisualizer.prototype._onCollectionChanged, this);
var entities = this._entityCollection.values;
for (var i = 0; i < entities.length; i++) {
this._cluster.removeBillboard(entities[i]);
}
return destroyObject(this);
};
BillboardVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
var i;
var entity;
var items = this._items;
var cluster = this._cluster;
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
if (defined(entity._billboard) && defined(entity._position)) {
items.set(entity.id, new EntityData(entity));
}
}
for (i = changed.length - 1; i > -1; i--) {
entity = changed[i];
if (defined(entity._billboard) && defined(entity._position)) {
if (!items.contains(entity.id)) {
items.set(entity.id, new EntityData(entity));
}
} else {
returnPrimitive(items.get(entity.id), entity, cluster);
items.remove(entity.id);
}
}
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
returnPrimitive(items.get(entity.id), entity, cluster);
items.remove(entity.id);
}
};
function returnPrimitive(item, entity, cluster) {
if (defined(item)) {
item.billboard = undefined;
cluster.removeBillboard(entity);
}
}
return BillboardVisualizer;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/AllMaterialAppearanceFS',[],function() {
'use strict';
return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec3 v_tangentEC;\n\
varying vec3 v_binormalEC;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
vec3 positionToEyeEC = -v_positionEC;\n\
mat3 tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(v_normalEC, v_tangentEC, v_binormalEC);\n\
vec3 normalEC = normalize(v_normalEC);\n\
#ifdef FACE_FORWARD\n\
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
czm_materialInput materialInput;\n\
materialInput.normalEC = normalEC;\n\
materialInput.tangentToEyeMatrix = tangentToEyeMatrix;\n\
materialInput.positionToEyeEC = positionToEyeEC;\n\
materialInput.st = v_st;\n\
czm_material material = czm_getMaterial(materialInput);\n\
#ifdef FLAT\n\
gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/AllMaterialAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec3 tangent;\n\
attribute vec3 binormal;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec3 v_tangentEC;\n\
varying vec3 v_binormalEC;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
vec4 p = czm_computePosition();\n\
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;\n\
v_normalEC = czm_normal * normal;\n\
v_tangentEC = czm_normal * tangent;\n\
v_binormalEC = czm_normal * binormal;\n\
v_st = st;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/BasicMaterialAppearanceFS',[],function() {
'use strict';
return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
void main()\n\
{\n\
vec3 positionToEyeEC = -v_positionEC;\n\
vec3 normalEC = normalize(v_normalEC);\n\
#ifdef FACE_FORWARD\n\
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
czm_materialInput materialInput;\n\
materialInput.normalEC = normalEC;\n\
materialInput.positionToEyeEC = positionToEyeEC;\n\
czm_material material = czm_getMaterial(materialInput);\n\
#ifdef FLAT\n\
gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/BasicMaterialAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute float batchId;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
void main()\n\
{\n\
vec4 p = czm_computePosition();\n\
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;\n\
v_normalEC = czm_normal * normal;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/TexturedMaterialAppearanceFS',[],function() {
'use strict';
return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
vec3 positionToEyeEC = -v_positionEC;\n\
vec3 normalEC = normalize(v_normalEC);;\n\
#ifdef FACE_FORWARD\n\
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
czm_materialInput materialInput;\n\
materialInput.normalEC = normalEC;\n\
materialInput.positionToEyeEC = positionToEyeEC;\n\
materialInput.st = v_st;\n\
czm_material material = czm_getMaterial(materialInput);\n\
#ifdef FLAT\n\
gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/TexturedMaterialAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
vec4 p = czm_computePosition();\n\
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;\n\
v_normalEC = czm_normal * normal;\n\
v_st = st;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/BlendEquation',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Determines how two pixels' values are combined.
*
* @exports BlendEquation
*/
var BlendEquation = {
/**
* Pixel values are added componentwise. This is used in additive blending for translucency.
*
* @type {Number}
* @constant
*/
ADD : WebGLConstants.FUNC_ADD,
/**
* Pixel values are subtracted componentwise (source - destination). This is used in alpha blending for translucency.
*
* @type {Number}
* @constant
*/
SUBTRACT : WebGLConstants.FUNC_SUBTRACT,
/**
* Pixel values are subtracted componentwise (destination - source).
*
* @type {Number}
* @constant
*/
REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT
// No min and max like in ColladaFX GLES2 profile
};
return freezeObject(BlendEquation);
});
/*global define*/
define('Scene/BlendFunction',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Determines how blending factors are computed.
*
* @exports BlendFunction
*/
var BlendFunction = {
/**
* The blend factor is zero.
*
* @type {Number}
* @constant
*/
ZERO : WebGLConstants.ZERO,
/**
* The blend factor is one.
*
* @type {Number}
* @constant
*/
ONE : WebGLConstants.ONE,
/**
* The blend factor is the source color.
*
* @type {Number}
* @constant
*/
SOURCE_COLOR : WebGLConstants.SRC_COLOR,
/**
* The blend factor is one minus the source color.
*
* @type {Number}
* @constant
*/
ONE_MINUS_SOURCE_COLOR : WebGLConstants.ONE_MINUS_SRC_COLOR,
/**
* The blend factor is the destination color.
*
* @type {Number}
* @constant
*/
DESTINATION_COLOR : WebGLConstants.DST_COLOR,
/**
* The blend factor is one minus the destination color.
*
* @type {Number}
* @constant
*/
ONE_MINUS_DESTINATION_COLOR : WebGLConstants.ONE_MINUS_DST_COLOR,
/**
* The blend factor is the source alpha.
*
* @type {Number}
* @constant
*/
SOURCE_ALPHA : WebGLConstants.SRC_ALPHA,
/**
* The blend factor is one minus the source alpha.
*
* @type {Number}
* @constant
*/
ONE_MINUS_SOURCE_ALPHA : WebGLConstants.ONE_MINUS_SRC_ALPHA,
/**
* The blend factor is the destination alpha.
*
* @type {Number}
* @constant
*/
DESTINATION_ALPHA : WebGLConstants.DST_ALPHA,
/**
* The blend factor is one minus the destination alpha.
*
* @type {Number}
* @constant
*/
ONE_MINUS_DESTINATION_ALPHA : WebGLConstants.ONE_MINUS_DST_ALPHA,
/**
* The blend factor is the constant color.
*
* @type {Number}
* @constant
*/
CONSTANT_COLOR : WebGLConstants.CONSTANT_COLOR,
/**
* The blend factor is one minus the constant color.
*
* @type {Number}
* @constant
*/
ONE_MINUS_CONSTANT_COLOR : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA,
/**
* The blend factor is the constant alpha.
*
* @type {Number}
* @constant
*/
CONSTANT_ALPHA : WebGLConstants.CONSTANT_ALPHA,
/**
* The blend factor is one minus the constant alpha.
*
* @type {Number}
* @constant
*/
ONE_MINUS_CONSTANT_ALPHA : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA,
/**
* The blend factor is the saturated source alpha.
*
* @type {Number}
* @constant
*/
SOURCE_ALPHA_SATURATE : WebGLConstants.SRC_ALPHA_SATURATE
};
return freezeObject(BlendFunction);
});
/*global define*/
define('Scene/BlendingState',[
'../Core/freezeObject',
'./BlendEquation',
'./BlendFunction'
], function(
freezeObject,
BlendEquation,
BlendFunction) {
'use strict';
/**
* The blending state combines {@link BlendEquation} and {@link BlendFunction} and the
* enabled
flag to define the full blending state for combining source and
* destination fragments when rendering.
*
* This is a helper when using custom render states with {@link Appearance#renderState}.
*
*
* @exports BlendingState
*/
var BlendingState = {
/**
* Blending is disabled.
*
* @type {Object}
* @constant
*/
DISABLED : freezeObject({
enabled : false
}),
/**
* Blending is enabled using alpha blending, source(source.alpha) + destination(1 - source.alpha)
.
*
* @type {Object}
* @constant
*/
ALPHA_BLEND : freezeObject({
enabled : true,
equationRgb : BlendEquation.ADD,
equationAlpha : BlendEquation.ADD,
functionSourceRgb : BlendFunction.SOURCE_ALPHA,
functionSourceAlpha : BlendFunction.SOURCE_ALPHA,
functionDestinationRgb : BlendFunction.ONE_MINUS_SOURCE_ALPHA,
functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
}),
/**
* Blending is enabled using alpha blending with premultiplied alpha, source + destination(1 - source.alpha)
.
*
* @type {Object}
* @constant
*/
PRE_MULTIPLIED_ALPHA_BLEND : freezeObject({
enabled : true,
equationRgb : BlendEquation.ADD,
equationAlpha : BlendEquation.ADD,
functionSourceRgb : BlendFunction.ONE,
functionSourceAlpha : BlendFunction.ONE,
functionDestinationRgb : BlendFunction.ONE_MINUS_SOURCE_ALPHA,
functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
}),
/**
* Blending is enabled using additive blending, source(source.alpha) + destination
.
*
* @type {Object}
* @constant
*/
ADDITIVE_BLEND : freezeObject({
enabled : true,
equationRgb : BlendEquation.ADD,
equationAlpha : BlendEquation.ADD,
functionSourceRgb : BlendFunction.SOURCE_ALPHA,
functionSourceAlpha : BlendFunction.SOURCE_ALPHA,
functionDestinationRgb : BlendFunction.ONE,
functionDestinationAlpha : BlendFunction.ONE
})
};
return freezeObject(BlendingState);
});
/*global define*/
define('Scene/CullFace',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Determines which triangles, if any, are culled.
*
* @exports CullFace
*/
var CullFace = {
/**
* Front-facing triangles are culled.
*
* @type {Number}
* @constant
*/
FRONT : WebGLConstants.FRONT,
/**
* Back-facing triangles are culled.
*
* @type {Number}
* @constant
*/
BACK : WebGLConstants.BACK,
/**
* Both front-facing and back-facing triangles are culled.
*
* @type {Number}
* @constant
*/
FRONT_AND_BACK : WebGLConstants.FRONT_AND_BACK
};
return freezeObject(CullFace);
});
/*global define*/
define('Scene/Appearance',[
'../Core/clone',
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'./BlendingState',
'./CullFace'
], function(
clone,
combine,
defaultValue,
defined,
defineProperties,
BlendingState,
CullFace) {
'use strict';
/**
* An appearance defines the full GLSL vertex and fragment shaders and the
* render state used to draw a {@link Primitive}. All appearances implement
* this base Appearance
interface.
*
* @alias Appearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.translucent=true] When true
, the geometry is expected to appear translucent so {@link Appearance#renderState} has alpha blending enabled.
* @param {Boolean} [options.closed=false] When true
, the geometry is expected to be closed so {@link Appearance#renderState} has backface culling enabled.
* @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @see MaterialAppearance
* @see EllipsoidSurfaceAppearance
* @see PerInstanceColorAppearance
* @see DebugAppearance
* @see PolylineColorAppearance
* @see PolylineMaterialAppearance
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Geometry%20and%20Appearances.html|Geometry and Appearances Demo}
*/
function Appearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The material used to determine the fragment color. Unlike other {@link Appearance}
* properties, this is not read-only, so an appearance's material can change on the fly.
*
* @type Material
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = options.material;
/**
* When true
, the geometry is expected to appear translucent.
*
* @type {Boolean}
*
* @default true
*/
this.translucent = defaultValue(options.translucent, true);
this._vertexShaderSource = options.vertexShaderSource;
this._fragmentShaderSource = options.fragmentShaderSource;
this._renderState = options.renderState;
this._closed = defaultValue(options.closed, false);
}
defineProperties(Appearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof Appearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader. The full fragment shader
* source is built procedurally taking into account the {@link Appearance#material}.
* Use {@link Appearance#getFragmentShaderSource} to get the full source.
*
* @memberof Appearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* @memberof Appearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed.
*
* @memberof Appearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
}
});
/**
* Procedurally creates the full GLSL fragment shader source for this appearance
* taking into account {@link Appearance#fragmentShaderSource} and {@link Appearance#material}.
*
* @returns {String} The full GLSL fragment shader source.
*/
Appearance.prototype.getFragmentShaderSource = function() {
var parts = [];
if (this.flat) {
parts.push('#define FLAT');
}
if (this.faceForward) {
parts.push('#define FACE_FORWARD');
}
if (defined(this.material)) {
parts.push(this.material.shaderSource);
}
parts.push(this.fragmentShaderSource);
return parts.join('\n');
};
/**
* Determines if the geometry is translucent based on {@link Appearance#translucent} and {@link Material#isTranslucent}.
*
* @returns {Boolean} true
if the appearance is translucent.
*/
Appearance.prototype.isTranslucent = function() {
return (defined(this.material) && this.material.isTranslucent()) || (!defined(this.material) && this.translucent);
};
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @returns {Object} The render state.
*/
Appearance.prototype.getRenderState = function() {
var translucent = this.isTranslucent();
var rs = clone(this.renderState, false);
if (translucent) {
rs.depthMask = false;
rs.blending = BlendingState.ALPHA_BLEND;
} else {
rs.depthMask = true;
}
return rs;
};
/**
* @private
*/
Appearance.getDefaultRenderState = function(translucent, closed, existing) {
var rs = {
depthTest : {
enabled : true
}
};
if (translucent) {
rs.depthMask = false;
rs.blending = BlendingState.ALPHA_BLEND;
}
if (closed) {
rs.cull = {
enabled : true,
face : CullFace.BACK
};
}
if (defined(existing)) {
rs = combine(existing, rs, true);
}
return rs;
};
return Appearance;
});
/*global define*/
define('Renderer/ContextLimits',[
'../Core/defineProperties'
], function(
defineProperties) {
'use strict';
/**
* @private
*/
var ContextLimits = {
_maximumCombinedTextureImageUnits : 0,
_maximumCubeMapSize : 0,
_maximumFragmentUniformVectors : 0,
_maximumTextureImageUnits : 0,
_maximumRenderbufferSize : 0,
_maximumTextureSize : 0,
_maximumVaryingVectors : 0,
_maximumVertexAttributes : 0,
_maximumVertexTextureImageUnits : 0,
_maximumVertexUniformVectors : 0,
_minimumAliasedLineWidth : 0,
_maximumAliasedLineWidth : 0,
_minimumAliasedPointSize : 0,
_maximumAliasedPointSize : 0,
_maximumViewportWidth : 0,
_maximumViewportHeight : 0,
_maximumTextureFilterAnisotropy : 0,
_maximumDrawBuffers : 0,
_maximumColorAttachments : 0,
_highpFloatSupported: false,
_highpIntSupported: false
};
defineProperties(ContextLimits, {
/**
* The maximum number of texture units that can be used from the vertex and fragment
* shader with this WebGL implementation. The minimum is eight. If both shaders access the
* same texture unit, this counts as two texture units.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_COMBINED_TEXTURE_IMAGE_UNITS
.
*/
maximumCombinedTextureImageUnits : {
get: function () {
return ContextLimits._maximumCombinedTextureImageUnits;
}
},
/**
* The approximate maximum cube mape width and height supported by this WebGL implementation.
* The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_CUBE_MAP_TEXTURE_SIZE
.
*/
maximumCubeMapSize : {
get: function () {
return ContextLimits._maximumCubeMapSize;
}
},
/**
* The maximum number of vec4
, ivec4
, and bvec4
* uniforms that can be used by a fragment shader with this WebGL implementation. The minimum is 16.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_FRAGMENT_UNIFORM_VECTORS
.
*/
maximumFragmentUniformVectors : {
get: function () {
return ContextLimits._maximumFragmentUniformVectors;
}
},
/**
* The maximum number of texture units that can be used from the fragment shader with this WebGL implementation. The minimum is eight.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_TEXTURE_IMAGE_UNITS
.
*/
maximumTextureImageUnits : {
get: function () {
return ContextLimits._maximumTextureImageUnits;
}
},
/**
* The maximum renderbuffer width and height supported by this WebGL implementation.
* The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_RENDERBUFFER_SIZE
.
*/
maximumRenderbufferSize : {
get: function () {
return ContextLimits._maximumRenderbufferSize;
}
},
/**
* The approximate maximum texture width and height supported by this WebGL implementation.
* The minimum is 64, but most desktop and laptop implementations will support much larger sizes like 8,192.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_TEXTURE_SIZE
.
*/
maximumTextureSize : {
get: function () {
return ContextLimits._maximumTextureSize;
}
},
/**
* The maximum number of vec4
varying variables supported by this WebGL implementation.
* The minimum is eight. Matrices and arrays count as multiple vec4
s.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VARYING_VECTORS
.
*/
maximumVaryingVectors : {
get: function () {
return ContextLimits._maximumVaryingVectors;
}
},
/**
* The maximum number of vec4
vertex attributes supported by this WebGL implementation. The minimum is eight.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VERTEX_ATTRIBS
.
*/
maximumVertexAttributes : {
get: function () {
return ContextLimits._maximumVertexAttributes;
}
},
/**
* The maximum number of texture units that can be used from the vertex shader with this WebGL implementation.
* The minimum is zero, which means the GL does not support vertex texture fetch.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VERTEX_TEXTURE_IMAGE_UNITS
.
*/
maximumVertexTextureImageUnits : {
get: function () {
return ContextLimits._maximumVertexTextureImageUnits;
}
},
/**
* The maximum number of vec4
, ivec4
, and bvec4
* uniforms that can be used by a vertex shader with this WebGL implementation. The minimum is 16.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VERTEX_UNIFORM_VECTORS
.
*/
maximumVertexUniformVectors : {
get: function () {
return ContextLimits._maximumVertexUniformVectors;
}
},
/**
* The minimum aliased line width, in pixels, supported by this WebGL implementation. It will be at most one.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE
.
*/
minimumAliasedLineWidth : {
get: function () {
return ContextLimits._minimumAliasedLineWidth;
}
},
/**
* The maximum aliased line width, in pixels, supported by this WebGL implementation. It will be at least one.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE
.
*/
maximumAliasedLineWidth : {
get: function () {
return ContextLimits._maximumAliasedLineWidth;
}
},
/**
* The minimum aliased point size, in pixels, supported by this WebGL implementation. It will be at most one.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_POINT_SIZE_RANGE
.
*/
minimumAliasedPointSize : {
get: function () {
return ContextLimits._minimumAliasedPointSize;
}
},
/**
* The maximum aliased point size, in pixels, supported by this WebGL implementation. It will be at least one.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_POINT_SIZE_RANGE
.
*/
maximumAliasedPointSize : {
get: function () {
return ContextLimits._maximumAliasedPointSize;
}
},
/**
* The maximum supported width of the viewport. It will be at least as large as the visible width of the associated canvas.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VIEWPORT_DIMS
.
*/
maximumViewportWidth : {
get: function () {
return ContextLimits._maximumViewportWidth;
}
},
/**
* The maximum supported height of the viewport. It will be at least as large as the visible height of the associated canvas.
* @memberof ContextLimits
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with MAX_VIEWPORT_DIMS
.
*/
maximumViewportHeight : {
get: function () {
return ContextLimits._maximumViewportHeight;
}
},
/**
* The maximum degree of anisotropy for texture filtering
* @memberof ContextLimits
* @type {Number}
*/
maximumTextureFilterAnisotropy : {
get: function () {
return ContextLimits._maximumTextureFilterAnisotropy;
}
},
/**
* The maximum number of simultaneous outputs that may be written in a fragment shader.
* @memberof ContextLimits
* @type {Number}
*/
maximumDrawBuffers : {
get: function () {
return ContextLimits._maximumDrawBuffers;
}
},
/**
* The maximum number of color attachments supported.
* @memberof ContextLimits
* @type {Number}
*/
maximumColorAttachments : {
get: function () {
return ContextLimits._maximumColorAttachments;
}
},
/**
* High precision float supported (highp
) in fragment shaders.
* @memberof ContextLimits
* @type {Boolean}
*/
highpFloatSupported : {
get: function () {
return ContextLimits._highpFloatSupported;
}
},
/**
* High precision int supported (highp
) in fragment shaders.
* @memberof ContextLimits
* @type {Boolean}
*/
highpIntSupported : {
get: function () {
return ContextLimits._highpIntSupported;
}
}
});
return ContextLimits;
});
/*global define*/
define('Renderer/PixelDatatype',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var PixelDatatype = {
UNSIGNED_BYTE : WebGLConstants.UNSIGNED_BYTE,
UNSIGNED_SHORT : WebGLConstants.UNSIGNED_SHORT,
UNSIGNED_INT : WebGLConstants.UNSIGNED_INT,
FLOAT : WebGLConstants.FLOAT,
UNSIGNED_INT_24_8 : WebGLConstants.UNSIGNED_INT_24_8,
UNSIGNED_SHORT_4_4_4_4 : WebGLConstants.UNSIGNED_SHORT_4_4_4_4,
UNSIGNED_SHORT_5_5_5_1 : WebGLConstants.UNSIGNED_SHORT_5_5_5_1,
UNSIGNED_SHORT_5_6_5 : WebGLConstants.UNSIGNED_SHORT_5_6_5,
validate : function(pixelDatatype) {
return ((pixelDatatype === PixelDatatype.UNSIGNED_BYTE) ||
(pixelDatatype === PixelDatatype.UNSIGNED_SHORT) ||
(pixelDatatype === PixelDatatype.UNSIGNED_INT) ||
(pixelDatatype === PixelDatatype.FLOAT) ||
(pixelDatatype === PixelDatatype.UNSIGNED_INT_24_8) ||
(pixelDatatype === PixelDatatype.UNSIGNED_SHORT_4_4_4_4) ||
(pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_5_5_1) ||
(pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_6_5));
}
};
return freezeObject(PixelDatatype);
});
/*global define*/
define('Renderer/CubeMapFace',[
'../Core/defaultValue',
'../Core/defineProperties',
'../Core/DeveloperError',
'./PixelDatatype'
], function(
defaultValue,
defineProperties,
DeveloperError,
PixelDatatype) {
'use strict';
/**
* @private
*/
function CubeMapFace(gl, texture, textureTarget, targetFace, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY) {
this._gl = gl;
this._texture = texture;
this._textureTarget = textureTarget;
this._targetFace = targetFace;
this._pixelFormat = pixelFormat;
this._pixelDatatype = pixelDatatype;
this._size = size;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
}
defineProperties(CubeMapFace.prototype, {
pixelFormat : {
get : function() {
return this._pixelFormat;
}
},
pixelDatatype : {
get : function() {
return this._pixelDatatype;
}
},
_target : {
get : function() {
return this._targetFace;
}
}
});
/**
* Copies texels from the source to the cubemap's face.
*
* @param {Object} source The source ImageData, HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, or an object with a width, height, and typed array as shown in the example.
* @param {Number} [xOffset=0] An offset in the x direction in the cubemap where copying begins.
* @param {Number} [yOffset=0] An offset in the y direction in the cubemap where copying begins.
*
* @exception {DeveloperError} xOffset must be greater than or equal to zero.
* @exception {DeveloperError} yOffset must be greater than or equal to zero.
* @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
* @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
* @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
*
* @example
* // Create a cubemap with 1x1 faces, and make the +x face red.
* var cubeMap = new CubeMap({
* context : context
* width : 1,
* height : 1
* });
* cubeMap.positiveX.copyFrom({
* width : 1,
* height : 1,
* arrayBufferView : new Uint8Array([255, 0, 0, 255])
* });
*/
CubeMapFace.prototype.copyFrom = function(source, xOffset, yOffset) {
xOffset = defaultValue(xOffset, 0);
yOffset = defaultValue(yOffset, 0);
if (!source) {
throw new DeveloperError('source is required.');
}
if (xOffset < 0) {
throw new DeveloperError('xOffset must be greater than or equal to zero.');
}
if (yOffset < 0) {
throw new DeveloperError('yOffset must be greater than or equal to zero.');
}
if (xOffset + source.width > this._size) {
throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
}
if (yOffset + source.height > this._size) {
throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
}
var gl = this._gl;
var target = this._textureTarget;
// TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this._flipY);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
if (source.arrayBufferView) {
gl.texSubImage2D(this._targetFace, 0, xOffset, yOffset, source.width, source.height, this._pixelFormat, this._pixelDatatype, source.arrayBufferView);
} else {
gl.texSubImage2D(this._targetFace, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
}
gl.bindTexture(target, null);
};
/**
* Copies texels from the framebuffer to the cubemap's face.
*
* @param {Number} [xOffset=0] An offset in the x direction in the cubemap where copying begins.
* @param {Number} [yOffset=0] An offset in the y direction in the cubemap where copying begins.
* @param {Number} [framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
* @param {Number} [framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
* @param {Number} [width=CubeMap's width] The width of the subimage to copy.
* @param {Number} [height=CubeMap's height] The height of the subimage to copy.
*
* @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
* @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
* @exception {DeveloperError} xOffset must be greater than or equal to zero.
* @exception {DeveloperError} yOffset must be greater than or equal to zero.
* @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
* @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
* @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
* @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
* @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
*
* @example
* // Copy the framebuffer contents to the +x cube map face.
* cubeMap.positiveX.copyFromFramebuffer();
*/
CubeMapFace.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
xOffset = defaultValue(xOffset, 0);
yOffset = defaultValue(yOffset, 0);
framebufferXOffset = defaultValue(framebufferXOffset, 0);
framebufferYOffset = defaultValue(framebufferYOffset, 0);
width = defaultValue(width, this._size);
height = defaultValue(height, this._size);
if (xOffset < 0) {
throw new DeveloperError('xOffset must be greater than or equal to zero.');
}
if (yOffset < 0) {
throw new DeveloperError('yOffset must be greater than or equal to zero.');
}
if (framebufferXOffset < 0) {
throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
}
if (framebufferYOffset < 0) {
throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
}
if (xOffset + width > this._size) {
throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
}
if (yOffset + height > this._size) {
throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
}
if (this._pixelDatatype === PixelDatatype.FLOAT) {
throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.');
}
var gl = this._gl;
var target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.copyTexSubImage2D(this._targetFace, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
gl.bindTexture(target, null);
};
return CubeMapFace;
});
/*global define*/
define('Renderer/MipmapHint',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var MipmapHint = {
DONT_CARE : WebGLConstants.DONT_CARE,
FASTEST : WebGLConstants.FASTEST,
NICEST : WebGLConstants.NICEST,
validate : function(mipmapHint) {
return ((mipmapHint === MipmapHint.DONT_CARE) ||
(mipmapHint === MipmapHint.FASTEST) ||
(mipmapHint === MipmapHint.NICEST));
}
};
return freezeObject(MipmapHint);
});
/*global define*/
define('Renderer/TextureMagnificationFilter',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var TextureMagnificationFilter = {
NEAREST : WebGLConstants.NEAREST,
LINEAR : WebGLConstants.LINEAR,
validate : function(textureMagnificationFilter) {
return ((textureMagnificationFilter === TextureMagnificationFilter.NEAREST) ||
(textureMagnificationFilter === TextureMagnificationFilter.LINEAR));
}
};
return freezeObject(TextureMagnificationFilter);
});
/*global define*/
define('Renderer/TextureMinificationFilter',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var TextureMinificationFilter = {
NEAREST : WebGLConstants.NEAREST,
LINEAR : WebGLConstants.LINEAR,
NEAREST_MIPMAP_NEAREST : WebGLConstants.NEAREST_MIPMAP_NEAREST,
LINEAR_MIPMAP_NEAREST : WebGLConstants.LINEAR_MIPMAP_NEAREST,
NEAREST_MIPMAP_LINEAR : WebGLConstants.NEAREST_MIPMAP_LINEAR,
LINEAR_MIPMAP_LINEAR : WebGLConstants.LINEAR_MIPMAP_LINEAR,
validate : function(textureMinificationFilter) {
return ((textureMinificationFilter === TextureMinificationFilter.NEAREST) ||
(textureMinificationFilter === TextureMinificationFilter.LINEAR) ||
(textureMinificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
(textureMinificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
(textureMinificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
(textureMinificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR));
}
};
return freezeObject(TextureMinificationFilter);
});
/*global define*/
define('Renderer/TextureWrap',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var TextureWrap = {
CLAMP_TO_EDGE : WebGLConstants.CLAMP_TO_EDGE,
REPEAT : WebGLConstants.REPEAT,
MIRRORED_REPEAT : WebGLConstants.MIRRORED_REPEAT,
validate : function(textureWrap) {
return ((textureWrap === TextureWrap.CLAMP_TO_EDGE) ||
(textureWrap === TextureWrap.REPEAT) ||
(textureWrap === TextureWrap.MIRRORED_REPEAT));
}
};
return freezeObject(TextureWrap);
});
/*global define*/
define('Renderer/Sampler',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'./TextureMagnificationFilter',
'./TextureMinificationFilter',
'./TextureWrap'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
TextureMagnificationFilter,
TextureMinificationFilter,
TextureWrap) {
'use strict';
/**
* @private
*/
function Sampler(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var wrapS = defaultValue(options.wrapS, TextureWrap.CLAMP_TO_EDGE);
var wrapT = defaultValue(options.wrapT, TextureWrap.CLAMP_TO_EDGE);
var minificationFilter = defaultValue(options.minificationFilter, TextureMinificationFilter.LINEAR);
var magnificationFilter = defaultValue(options.magnificationFilter, TextureMagnificationFilter.LINEAR);
var maximumAnisotropy = (defined(options.maximumAnisotropy)) ? options.maximumAnisotropy : 1.0;
if (!TextureWrap.validate(wrapS)) {
throw new DeveloperError('Invalid sampler.wrapS.');
}
if (!TextureWrap.validate(wrapT)) {
throw new DeveloperError('Invalid sampler.wrapT.');
}
if (!TextureMinificationFilter.validate(minificationFilter)) {
throw new DeveloperError('Invalid sampler.minificationFilter.');
}
if (!TextureMagnificationFilter.validate(magnificationFilter)) {
throw new DeveloperError('Invalid sampler.magnificationFilter.');
}
if (maximumAnisotropy < 1.0) {
throw new DeveloperError('sampler.maximumAnisotropy must be greater than or equal to one.');
}
this._wrapS = wrapS;
this._wrapT = wrapT;
this._minificationFilter = minificationFilter;
this._magnificationFilter = magnificationFilter;
this._maximumAnisotropy = maximumAnisotropy;
}
defineProperties(Sampler.prototype, {
wrapS : {
get : function() {
return this._wrapS;
}
},
wrapT : {
get : function() {
return this._wrapT;
}
},
minificationFilter : {
get : function() {
return this._minificationFilter;
}
},
magnificationFilter : {
get : function() {
return this._magnificationFilter;
}
},
maximumAnisotropy : {
get : function() {
return this._maximumAnisotropy;
}
}
});
return Sampler;
});
/*global define*/
define('Renderer/CubeMap',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Math',
'../Core/PixelFormat',
'./ContextLimits',
'./CubeMapFace',
'./MipmapHint',
'./PixelDatatype',
'./Sampler',
'./TextureMagnificationFilter',
'./TextureMinificationFilter'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
CesiumMath,
PixelFormat,
ContextLimits,
CubeMapFace,
MipmapHint,
PixelDatatype,
Sampler,
TextureMagnificationFilter,
TextureMinificationFilter) {
'use strict';
function CubeMap(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
var context = options.context;
var source = options.source;
var width;
var height;
if (defined(source)) {
var faces = [source.positiveX, source.negativeX, source.positiveY, source.negativeY, source.positiveZ, source.negativeZ];
if (!faces[0] || !faces[1] || !faces[2] || !faces[3] || !faces[4] || !faces[5]) {
throw new DeveloperError('options.source requires positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ faces.');
}
width = faces[0].width;
height = faces[0].height;
for ( var i = 1; i < 6; ++i) {
if ((Number(faces[i].width) !== width) || (Number(faces[i].height) !== height)) {
throw new DeveloperError('Each face in options.source must have the same width and height.');
}
}
} else {
width = options.width;
height = options.height;
}
var size = width;
var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);
if (!defined(width) || !defined(height)) {
throw new DeveloperError('options requires a source field to create an initialized cube map or width and height fields to create a blank cube map.');
}
if (width !== height) {
throw new DeveloperError('Width must equal height.');
}
if (size <= 0) {
throw new DeveloperError('Width and height must be greater than zero.');
}
if (size > ContextLimits.maximumCubeMapSize) {
throw new DeveloperError('Width and height must be less than or equal to the maximum cube map size (' + ContextLimits.maximumCubeMapSize + '). Check maximumCubeMapSize.');
}
if (!PixelFormat.validate(pixelFormat)) {
throw new DeveloperError('Invalid options.pixelFormat.');
}
if (PixelFormat.isDepthFormat(pixelFormat)) {
throw new DeveloperError('options.pixelFormat cannot be DEPTH_COMPONENT or DEPTH_STENCIL.');
}
if (!PixelDatatype.validate(pixelDatatype)) {
throw new DeveloperError('Invalid options.pixelDatatype.');
}
if ((pixelDatatype === PixelDatatype.FLOAT) && !context.floatingPointTexture) {
throw new DeveloperError('When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension.');
}
// Use premultiplied alpha for opaque textures should perform better on Chrome:
// http://media.tojicode.com/webglCamp4/#20
var preMultiplyAlpha = options.preMultiplyAlpha || ((pixelFormat === PixelFormat.RGB) || (pixelFormat === PixelFormat.LUMINANCE));
var flipY = defaultValue(options.flipY, true);
var gl = context._gl;
var textureTarget = gl.TEXTURE_CUBE_MAP;
var texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(textureTarget, texture);
function createFace(target, sourceFace) {
if (sourceFace.arrayBufferView) {
gl.texImage2D(target, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, sourceFace.arrayBufferView);
} else {
gl.texImage2D(target, 0, pixelFormat, pixelFormat, pixelDatatype, sourceFace);
}
}
if (defined(source)) {
// TODO: _gl.pixelStorei(_gl._UNPACK_ALIGNMENT, 4);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
createFace(gl.TEXTURE_CUBE_MAP_POSITIVE_X, source.positiveX);
createFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, source.negativeX);
createFace(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, source.positiveY);
createFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, source.negativeY);
createFace(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, source.positiveZ);
createFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, source.negativeZ);
} else {
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
}
gl.bindTexture(textureTarget, null);
this._gl = gl;
this._textureFilterAnisotropic = context._textureFilterAnisotropic;
this._textureTarget = textureTarget;
this._texture = texture;
this._pixelFormat = pixelFormat;
this._pixelDatatype = pixelDatatype;
this._size = size;
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
this._sampler = undefined;
this._positiveX = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_X, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
this._negativeX = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
this._positiveY = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
this._negativeY = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
this._positiveZ = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_Z, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
this._negativeZ = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
}
defineProperties(CubeMap.prototype, {
positiveX : {
get : function() {
return this._positiveX;
}
},
negativeX : {
get : function() {
return this._negativeX;
}
},
positiveY : {
get : function() {
return this._positiveY;
}
},
negativeY : {
get : function() {
return this._negativeY;
}
},
positiveZ : {
get : function() {
return this._positiveZ;
}
},
negativeZ : {
get : function() {
return this._negativeZ;
}
},
sampler : {
get : function() {
return this._sampler;
},
set : function(sampler) {
var minificationFilter = sampler.minificationFilter;
var magnificationFilter = sampler.magnificationFilter;
var mipmap =
(minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
(minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
(minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
(minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
// float textures only support nearest filtering, so override the sampler's settings
if (this._pixelDatatype === PixelDatatype.FLOAT) {
minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST;
magnificationFilter = TextureMagnificationFilter.NEAREST;
}
var gl = this._gl;
var target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
if (defined(this._textureFilterAnisotropic)) {
gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
}
gl.bindTexture(target, null);
this._sampler = sampler;
}
},
pixelFormat: {
get : function() {
return this._pixelFormat;
}
},
pixelDatatype : {
get : function() {
return this._pixelDatatype;
}
},
width : {
get : function() {
return this._size;
}
},
height: {
get : function() {
return this._size;
}
},
preMultiplyAlpha : {
get : function() {
return this._preMultiplyAlpha;
}
},
flipY : {
get : function() {
return this._flipY;
}
},
_target : {
get : function() {
return this._textureTarget;
}
}
});
/**
* Generates a complete mipmap chain for each cubemap face.
*
* @param {MipmapHint} [hint=MipmapHint.DONT_CARE] A performance vs. quality hint.
*
* @exception {DeveloperError} hint is invalid.
* @exception {DeveloperError} This CubeMap's width must be a power of two to call generateMipmap().
* @exception {DeveloperError} This CubeMap's height must be a power of two to call generateMipmap().
* @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
*
* @example
* // Generate mipmaps, and then set the sampler so mipmaps are used for
* // minification when the cube map is sampled.
* cubeMap.generateMipmap();
* cubeMap.sampler = new Sampler({
* minificationFilter : Cesium.TextureMinificationFilter.NEAREST_MIPMAP_LINEAR
* });
*/
CubeMap.prototype.generateMipmap = function(hint) {
hint = defaultValue(hint, MipmapHint.DONT_CARE);
if ((this._size > 1) && !CesiumMath.isPowerOfTwo(this._size)) {
throw new DeveloperError('width and height must be a power of two to call generateMipmap().');
}
if (!MipmapHint.validate(hint)) {
throw new DeveloperError('hint is invalid.');
}
var gl = this._gl;
var target = this._textureTarget;
gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.generateMipmap(target);
gl.bindTexture(target, null);
};
CubeMap.prototype.isDestroyed = function() {
return false;
};
CubeMap.prototype.destroy = function() {
this._gl.deleteTexture(this._texture);
this._positiveX = destroyObject(this._positiveX);
this._negativeX = destroyObject(this._negativeX);
this._positiveY = destroyObject(this._positiveY);
this._negativeY = destroyObject(this._negativeY);
this._positiveZ = destroyObject(this._positiveZ);
this._negativeZ = destroyObject(this._negativeZ);
return destroyObject(this);
};
return CubeMap;
});
/*global define*/
define('Renderer/Texture',[
'../Core/Cartesian2',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Math',
'../Core/PixelFormat',
'../Core/WebGLConstants',
'./ContextLimits',
'./MipmapHint',
'./PixelDatatype',
'./Sampler',
'./TextureMagnificationFilter',
'./TextureMinificationFilter'
], function(
Cartesian2,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
CesiumMath,
PixelFormat,
WebGLConstants,
ContextLimits,
MipmapHint,
PixelDatatype,
Sampler,
TextureMagnificationFilter,
TextureMinificationFilter) {
'use strict';
function Texture(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
var context = options.context;
var width = options.width;
var height = options.height;
var source = options.source;
if (defined(source)) {
if (!defined(width)) {
width = defaultValue(source.videoWidth, source.width);
}
if (!defined(height)) {
height = defaultValue(source.videoHeight, source.height);
}
}
var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);
var internalFormat = pixelFormat;
if (context.webgl2) {
if (pixelFormat === PixelFormat.DEPTH_STENCIL) {
internalFormat = WebGLConstants.DEPTH24_STENCIL8;
} else if (pixelFormat === PixelFormat.DEPTH_COMPONENT) {
if (pixelDatatype === PixelDatatype.UNSIGNED_SHORT) {
internalFormat = WebGLConstants.DEPTH_COMPONENT16;
} else if (pixelDatatype === PixelDatatype.UNSIGNED_INT) {
internalFormat = WebGLConstants.DEPTH_COMPONENT24;
}
}
}
if (!defined(width) || !defined(height)) {
throw new DeveloperError('options requires a source field to create an initialized texture or width and height fields to create a blank texture.');
}
if (width <= 0) {
throw new DeveloperError('Width must be greater than zero.');
}
if (width > ContextLimits.maximumTextureSize) {
throw new DeveloperError('Width must be less than or equal to the maximum texture size (' + ContextLimits.maximumTextureSize + '). Check maximumTextureSize.');
}
if (height <= 0) {
throw new DeveloperError('Height must be greater than zero.');
}
if (height > ContextLimits.maximumTextureSize) {
throw new DeveloperError('Height must be less than or equal to the maximum texture size (' + ContextLimits.maximumTextureSize + '). Check maximumTextureSize.');
}
if (!PixelFormat.validate(pixelFormat)) {
throw new DeveloperError('Invalid options.pixelFormat.');
}
if (!PixelDatatype.validate(pixelDatatype)) {
throw new DeveloperError('Invalid options.pixelDatatype.');
}
if ((pixelFormat === PixelFormat.DEPTH_COMPONENT) &&
((pixelDatatype !== PixelDatatype.UNSIGNED_SHORT) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT))) {
throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT, options.pixelDatatype must be UNSIGNED_SHORT or UNSIGNED_INT.');
}
if ((pixelFormat === PixelFormat.DEPTH_STENCIL) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT_24_8)) {
throw new DeveloperError('When options.pixelFormat is DEPTH_STENCIL, options.pixelDatatype must be UNSIGNED_INT_24_8.');
}
if ((pixelDatatype === PixelDatatype.FLOAT) && !context.floatingPointTexture) {
throw new DeveloperError('When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension. Check context.floatingPointTexture.');
}
if (PixelFormat.isDepthFormat(pixelFormat)) {
if (defined(source)) {
throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, source cannot be provided.');
}
if (!context.depthTexture) {
throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, this WebGL implementation must support WEBGL_depth_texture. Check context.depthTexture.');
}
}
// Use premultiplied alpha for opaque textures should perform better on Chrome:
// http://media.tojicode.com/webglCamp4/#20
var preMultiplyAlpha = options.preMultiplyAlpha || pixelFormat === PixelFormat.RGB || pixelFormat === PixelFormat.LUMINANCE;
var flipY = defaultValue(options.flipY, true);
var gl = context._gl;
var textureTarget = gl.TEXTURE_2D;
var texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(textureTarget, texture);
if (defined(source)) {
// TODO: _gl.pixelStorei(_gl._UNPACK_ALIGNMENT, 4);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
if (defined(source.arrayBufferView)) {
// Source: typed array
gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, source.arrayBufferView);
} else if (defined(source.framebuffer)) {
// Source: framebuffer
if (source.framebuffer !== context.defaultFramebuffer) {
source.framebuffer._bind();
}
gl.copyTexImage2D(textureTarget, 0, internalFormat, source.xOffset, source.yOffset, width, height, 0);
if (source.framebuffer !== context.defaultFramebuffer) {
source.framebuffer._unBind();
}
} else {
// Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
gl.texImage2D(textureTarget, 0, internalFormat, pixelFormat, pixelDatatype, source);
}
} else {
gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, null);
}
gl.bindTexture(textureTarget, null);
this._context = context;
this._textureFilterAnisotropic = context._textureFilterAnisotropic;
this._textureTarget = textureTarget;
this._texture = texture;
this._pixelFormat = pixelFormat;
this._pixelDatatype = pixelDatatype;
this._width = width;
this._height = height;
this._dimensions = new Cartesian2(width, height);
this._preMultiplyAlpha = preMultiplyAlpha;
this._flipY = flipY;
this._sampler = undefined;
this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
}
/**
* Creates a texture, and copies a subimage of the framebuffer to it. When called without arguments,
* the texture is the same width and height as the framebuffer and contains its contents.
*
* @param {Object} options Object with the following properties:
* @param {Context} options.context The context in which the Texture gets created.
* @param {PixelFormat} [options.pixelFormat=PixelFormat.RGB] The texture's internal pixel format.
* @param {Number} [options.framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
* @param {Number} [options.framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
* @param {Number} [options.width=canvas.clientWidth] The width of the texture in texels.
* @param {Number} [options.height=canvas.clientHeight] The height of the texture in texels.
* @param {Framebuffer} [options.framebuffer=defaultFramebuffer] The framebuffer from which to create the texture. If this
* parameter is not specified, the default framebuffer is used.
* @returns {Texture} A texture with contents from the framebuffer.
*
* @exception {DeveloperError} Invalid pixelFormat.
* @exception {DeveloperError} pixelFormat cannot be DEPTH_COMPONENT or DEPTH_STENCIL.
* @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
* @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
* @exception {DeveloperError} framebufferXOffset + width must be less than or equal to canvas.clientWidth.
* @exception {DeveloperError} framebufferYOffset + height must be less than or equal to canvas.clientHeight.
*
*
* @example
* // Create a texture with the contents of the framebuffer.
* var t = Texture.fromFramebuffer({
* context : context
* });
*
* @see Sampler
*
* @private
*/
Texture.fromFramebuffer = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
var context = options.context;
var gl = context._gl;
var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGB);
var framebufferXOffset = defaultValue(options.framebufferXOffset, 0);
var framebufferYOffset = defaultValue(options.framebufferYOffset, 0);
var width = defaultValue(options.width, gl.drawingBufferWidth);
var height = defaultValue(options.height, gl.drawingBufferHeight);
var framebuffer = options.framebuffer;
if (!defined(options.context)) {
throw new DeveloperError('context is required.');
}
if (!PixelFormat.validate(pixelFormat)) {
throw new DeveloperError('Invalid pixelFormat.');
}
if (PixelFormat.isDepthFormat(pixelFormat)) {
throw new DeveloperError('pixelFormat cannot be DEPTH_COMPONENT or DEPTH_STENCIL.');
}
if (framebufferXOffset < 0) {
throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
}
if (framebufferYOffset < 0) {
throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
}
if (framebufferXOffset + width > gl.drawingBufferWidth) {
throw new DeveloperError('framebufferXOffset + width must be less than or equal to drawingBufferWidth');
}
if (framebufferYOffset + height > gl.drawingBufferHeight) {
throw new DeveloperError('framebufferYOffset + height must be less than or equal to drawingBufferHeight.');
}
var texture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : pixelFormat,
source : {
framebuffer : defined(framebuffer) ? framebuffer : context.defaultFramebuffer,
xOffset : framebufferXOffset,
yOffset : framebufferYOffset,
width : width,
height : height
}
});
return texture;
};
defineProperties(Texture.prototype, {
/**
* The sampler to use when sampling this texture.
* Create a sampler by calling {@link Sampler}. If this
* parameter is not specified, a default sampler is used. The default sampler clamps texture
* coordinates in both directions, uses linear filtering for both magnification and minifcation,
* and uses a maximum anisotropy of 1.0.
* @memberof Texture.prototype
* @type {Object}
*/
sampler : {
get : function() {
return this._sampler;
},
set : function(sampler) {
var minificationFilter = sampler.minificationFilter;
var magnificationFilter = sampler.magnificationFilter;
var mipmap =
(minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
(minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
(minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
(minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
// float textures only support nearest filtering, so override the sampler's settings
if (this._pixelDatatype === PixelDatatype.FLOAT) {
minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST;
magnificationFilter = TextureMagnificationFilter.NEAREST;
}
var gl = this._context._gl;
var target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
if (defined(this._textureFilterAnisotropic)) {
gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
}
gl.bindTexture(target, null);
this._sampler = sampler;
}
},
pixelFormat : {
get : function() {
return this._pixelFormat;
}
},
pixelDatatype : {
get : function() {
return this._pixelDatatype;
}
},
dimensions : {
get : function() {
return this._dimensions;
}
},
preMultiplyAlpha : {
get : function() {
return this._preMultiplyAlpha;
}
},
flipY : {
get : function() {
return this._flipY;
}
},
width : {
get : function() {
return this._width;
}
},
height : {
get : function() {
return this._height;
}
},
_target : {
get : function() {
return this._textureTarget;
}
}
});
/**
* Copy new image data into this texture, from a source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video}.
* or an object with width, height, and arrayBufferView properties.
*
* @param {Object} source The source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video},
* or an object with width, height, and arrayBufferView properties.
* @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
* @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
*
* @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
* @exception {DeveloperError} xOffset must be greater than or equal to zero.
* @exception {DeveloperError} yOffset must be greater than or equal to zero.
* @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
* @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
* @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
*
* @example
* texture.copyFrom({
* width : 1,
* height : 1,
* arrayBufferView : new Uint8Array([255, 0, 0, 255])
* });
*/
Texture.prototype.copyFrom = function(source, xOffset, yOffset) {
xOffset = defaultValue(xOffset, 0);
yOffset = defaultValue(yOffset, 0);
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
if (PixelFormat.isDepthFormat(this._pixelFormat)) {
throw new DeveloperError('Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
}
if (xOffset < 0) {
throw new DeveloperError('xOffset must be greater than or equal to zero.');
}
if (yOffset < 0) {
throw new DeveloperError('yOffset must be greater than or equal to zero.');
}
if (xOffset + source.width > this._width) {
throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
}
if (yOffset + source.height > this._height) {
throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
}
var gl = this._context._gl;
var target = this._textureTarget;
// TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this._flipY);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
if (source.arrayBufferView) {
gl.texSubImage2D(target, 0, xOffset, yOffset, source.width, source.height, this._pixelFormat, this._pixelDatatype, source.arrayBufferView);
} else {
gl.texSubImage2D(target, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
}
gl.bindTexture(target, null);
};
/**
* @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
* @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
* @param {Number} [framebufferXOffset=0] optional
* @param {Number} [framebufferYOffset=0] optional
* @param {Number} [width=width] optional
* @param {Number} [height=height] optional
*
* @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
* @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
* @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
* @exception {DeveloperError} xOffset must be greater than or equal to zero.
* @exception {DeveloperError} yOffset must be greater than or equal to zero.
* @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
* @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
* @exception {DeveloperError} xOffset + width must be less than or equal to width.
* @exception {DeveloperError} yOffset + height must be less than or equal to height.
*/
Texture.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
xOffset = defaultValue(xOffset, 0);
yOffset = defaultValue(yOffset, 0);
framebufferXOffset = defaultValue(framebufferXOffset, 0);
framebufferYOffset = defaultValue(framebufferYOffset, 0);
width = defaultValue(width, this._width);
height = defaultValue(height, this._height);
if (PixelFormat.isDepthFormat(this._pixelFormat)) {
throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
}
if (this._pixelDatatype === PixelDatatype.FLOAT) {
throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.');
}
if (xOffset < 0) {
throw new DeveloperError('xOffset must be greater than or equal to zero.');
}
if (yOffset < 0) {
throw new DeveloperError('yOffset must be greater than or equal to zero.');
}
if (framebufferXOffset < 0) {
throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
}
if (framebufferYOffset < 0) {
throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
}
if (xOffset + width > this._width) {
throw new DeveloperError('xOffset + width must be less than or equal to width.');
}
if (yOffset + height > this._height) {
throw new DeveloperError('yOffset + height must be less than or equal to height.');
}
var gl = this._context._gl;
var target = this._textureTarget;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
gl.bindTexture(target, null);
};
/**
* @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
*
* @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
* @exception {DeveloperError} hint is invalid.
* @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
* @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
* @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
*/
Texture.prototype.generateMipmap = function(hint) {
hint = defaultValue(hint, MipmapHint.DONT_CARE);
if (PixelFormat.isDepthFormat(this._pixelFormat)) {
throw new DeveloperError('Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
}
if (this._width > 1 && !CesiumMath.isPowerOfTwo(this._width)) {
throw new DeveloperError('width must be a power of two to call generateMipmap().');
}
if (this._height > 1 && !CesiumMath.isPowerOfTwo(this._height)) {
throw new DeveloperError('height must be a power of two to call generateMipmap().');
}
if (!MipmapHint.validate(hint)) {
throw new DeveloperError('hint is invalid.');
}
var gl = this._context._gl;
var target = this._textureTarget;
gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(target, this._texture);
gl.generateMipmap(target);
gl.bindTexture(target, null);
};
Texture.prototype.isDestroyed = function() {
return false;
};
Texture.prototype.destroy = function() {
this._context._gl.deleteTexture(this._texture);
return destroyObject(this);
};
return Texture;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/BumpMapMaterial',[],function() {
'use strict';
return "uniform sampler2D image;\n\
uniform float strength;\n\
uniform vec2 repeat;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
vec2 centerPixel = fract(repeat * st);\n\
float centerBump = texture2D(image, centerPixel).channel;\n\
float imageWidth = float(imageDimensions.x);\n\
vec2 rightPixel = fract(repeat * (st + vec2(1.0 / imageWidth, 0.0)));\n\
float rightBump = texture2D(image, rightPixel).channel;\n\
float imageHeight = float(imageDimensions.y);\n\
vec2 leftPixel = fract(repeat * (st + vec2(0.0, 1.0 / imageHeight)));\n\
float topBump = texture2D(image, leftPixel).channel;\n\
vec3 normalTangentSpace = normalize(vec3(centerBump - rightBump, centerBump - topBump, clamp(1.0 - strength, 0.1, 1.0)));\n\
vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace;\n\
material.normal = normalEC;\n\
material.diffuse = vec3(0.01);\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/CheckerboardMaterial',[],function() {
'use strict';
return "uniform vec4 lightColor;\n\
uniform vec4 darkColor;\n\
uniform vec2 repeat;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
float b = mod(floor(repeat.s * st.s) + floor(repeat.t * st.t), 2.0);\n\
float scaledWidth = fract(repeat.s * st.s);\n\
scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5));\n\
float scaledHeight = fract(repeat.t * st.t);\n\
scaledHeight = abs(scaledHeight - floor(scaledHeight + 0.5));\n\
float value = min(scaledWidth, scaledHeight);\n\
vec4 currentColor = mix(lightColor, darkColor, b);\n\
vec4 color = czm_antialias(lightColor, darkColor, currentColor, value, 0.03);\n\
material.diffuse = color.rgb;\n\
material.alpha = color.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/DotMaterial',[],function() {
'use strict';
return "uniform vec4 lightColor;\n\
uniform vec4 darkColor;\n\
uniform vec2 repeat;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
float b = smoothstep(0.3, 0.32, length(fract(repeat * materialInput.st) - 0.5));\n\
vec4 color = mix(lightColor, darkColor, b);\n\
material.diffuse = color.rgb;\n\
material.alpha = color.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/FadeMaterial',[],function() {
'use strict';
return "uniform vec4 fadeInColor;\n\
uniform vec4 fadeOutColor;\n\
uniform float maximumDistance;\n\
uniform bool repeat;\n\
uniform vec2 fadeDirection;\n\
uniform vec2 time;\n\
float getTime(float t, float coord)\n\
{\n\
float scalar = 1.0 / maximumDistance;\n\
float q = distance(t, coord) * scalar;\n\
if (repeat)\n\
{\n\
float r = distance(t, coord + 1.0) * scalar;\n\
float s = distance(t, coord - 1.0) * scalar;\n\
q = min(min(r, s), q);\n\
}\n\
return clamp(q, 0.0, 1.0);\n\
}\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
float s = getTime(time.x, st.s) * fadeDirection.s;\n\
float t = getTime(time.y, st.t) * fadeDirection.t;\n\
float u = length(vec2(s, t));\n\
vec4 color = mix(fadeInColor, fadeOutColor, u);\n\
material.emission = color.rgb;\n\
material.alpha = color.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/GridMaterial',[],function() {
'use strict';
return "#ifdef GL_OES_standard_derivatives\n\
#extension GL_OES_standard_derivatives : enable\n\
#endif\n\
uniform vec4 color;\n\
uniform float cellAlpha;\n\
uniform vec2 lineCount;\n\
uniform vec2 lineThickness;\n\
uniform vec2 lineOffset;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
float scaledWidth = fract(lineCount.s * st.s - lineOffset.s);\n\
scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5));\n\
float scaledHeight = fract(lineCount.t * st.t - lineOffset.t);\n\
scaledHeight = abs(scaledHeight - floor(scaledHeight + 0.5));\n\
float value;\n\
#ifdef GL_OES_standard_derivatives\n\
const float fuzz = 1.2;\n\
vec2 thickness = (lineThickness * czm_resolutionScale) - 1.0;\n\
vec2 dx = abs(dFdx(st));\n\
vec2 dy = abs(dFdy(st));\n\
vec2 dF = vec2(max(dx.s, dy.s), max(dx.t, dy.t)) * lineCount;\n\
value = min(\n\
smoothstep(dF.s * thickness.s, dF.s * (fuzz + thickness.s), scaledWidth),\n\
smoothstep(dF.t * thickness.t, dF.t * (fuzz + thickness.t), scaledHeight));\n\
#else\n\
const float fuzz = 0.05;\n\
vec2 range = 0.5 - (lineThickness * 0.05);\n\
value = min(\n\
1.0 - smoothstep(range.s, range.s + fuzz, scaledWidth),\n\
1.0 - smoothstep(range.t, range.t + fuzz, scaledHeight));\n\
#endif\n\
float dRim = 1.0 - abs(dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC)));\n\
float sRim = smoothstep(0.8, 1.0, dRim);\n\
value *= (1.0 - sRim);\n\
vec3 halfColor = color.rgb * 0.5;\n\
material.diffuse = halfColor;\n\
material.emission = halfColor;\n\
material.alpha = color.a * (1.0 - ((1.0 - cellAlpha) * value));\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/NormalMapMaterial',[],function() {
'use strict';
return "uniform sampler2D image;\n\
uniform float strength;\n\
uniform vec2 repeat;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec4 textureValue = texture2D(image, fract(repeat * materialInput.st));\n\
vec3 normalTangentSpace = textureValue.channels;\n\
normalTangentSpace.xy = normalTangentSpace.xy * 2.0 - 1.0;\n\
normalTangentSpace.z = clamp(1.0 - strength, 0.1, 1.0);\n\
normalTangentSpace = normalize(normalTangentSpace);\n\
vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace;\n\
material.normal = normalEC;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/PolylineArrowMaterial',[],function() {
'use strict';
return "#extension GL_OES_standard_derivatives : enable\n\
uniform vec4 color;\n\
varying float v_width;\n\
float getPointOnLine(vec2 p0, vec2 p1, float x)\n\
{\n\
float slope = (p0.y - p1.y) / (p0.x - p1.x);\n\
return slope * (x - p0.x) + p0.y;\n\
}\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
float base = 1.0 - abs(fwidth(st.s)) * 10.0;\n\
vec2 center = vec2(1.0, 0.5);\n\
float ptOnUpperLine = getPointOnLine(vec2(base, 1.0), center, st.s);\n\
float ptOnLowerLine = getPointOnLine(vec2(base, 0.0), center, st.s);\n\
float halfWidth = 0.15;\n\
float s = step(0.5 - halfWidth, st.t);\n\
s *= 1.0 - step(0.5 + halfWidth, st.t);\n\
s *= 1.0 - step(base, st.s);\n\
float t = step(base, materialInput.st.s);\n\
t *= 1.0 - step(ptOnUpperLine, st.t);\n\
t *= step(ptOnLowerLine, st.t);\n\
float dist;\n\
if (st.s < base)\n\
{\n\
float d1 = abs(st.t - (0.5 - halfWidth));\n\
float d2 = abs(st.t - (0.5 + halfWidth));\n\
dist = min(d1, d2);\n\
}\n\
else\n\
{\n\
float d1 = czm_infinity;\n\
if (st.t < 0.5 - halfWidth && st.t > 0.5 + halfWidth)\n\
{\n\
d1 = abs(st.s - base);\n\
}\n\
float d2 = abs(st.t - ptOnUpperLine);\n\
float d3 = abs(st.t - ptOnLowerLine);\n\
dist = min(min(d1, d2), d3);\n\
}\n\
vec4 outsideColor = vec4(0.0);\n\
vec4 currentColor = mix(outsideColor, color, clamp(s + t, 0.0, 1.0));\n\
vec4 outColor = czm_antialias(outsideColor, color, currentColor, dist);\n\
material.diffuse = outColor.rgb;\n\
material.alpha = outColor.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/PolylineGlowMaterial',[],function() {
'use strict';
return "uniform vec4 color;\n\
uniform float glowPower;\n\
varying float v_width;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
float glow = glowPower / abs(st.t - 0.5) - (glowPower / 0.5);\n\
material.emission = max(vec3(glow - 1.0 + color.rgb), color.rgb);\n\
material.alpha = clamp(0.0, 1.0, glow) * color.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/PolylineOutlineMaterial',[],function() {
'use strict';
return "uniform vec4 color;\n\
uniform vec4 outlineColor;\n\
uniform float outlineWidth;\n\
varying float v_width;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
vec2 st = materialInput.st;\n\
float halfInteriorWidth = 0.5 * (v_width - outlineWidth) / v_width;\n\
float b = step(0.5 - halfInteriorWidth, st.t);\n\
b *= 1.0 - step(0.5 + halfInteriorWidth, st.t);\n\
float d1 = abs(st.t - (0.5 - halfInteriorWidth));\n\
float d2 = abs(st.t - (0.5 + halfInteriorWidth));\n\
float dist = min(d1, d2);\n\
vec4 currentColor = mix(outlineColor, color, b);\n\
vec4 outColor = czm_antialias(outlineColor, color, currentColor, dist);\n\
material.diffuse = outColor.rgb;\n\
material.alpha = outColor.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/RimLightingMaterial',[],function() {
'use strict';
return "uniform vec4 color;\n\
uniform vec4 rimColor;\n\
uniform float width;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
float d = 1.0 - dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC));\n\
float s = smoothstep(1.0 - width, 1.0, d);\n\
material.diffuse = color.rgb;\n\
material.emission = rimColor.rgb * s;\n\
material.alpha = mix(color.a, rimColor.a, s);\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/StripeMaterial',[],function() {
'use strict';
return "uniform vec4 evenColor;\n\
uniform vec4 oddColor;\n\
uniform float offset;\n\
uniform float repeat;\n\
uniform bool horizontal;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
float coord = mix(materialInput.st.s, materialInput.st.t, float(horizontal));\n\
float value = fract((coord - offset) * (repeat * 0.5));\n\
float dist = min(value, min(abs(value - 0.5), 1.0 - value));\n\
vec4 currentColor = mix(evenColor, oddColor, step(0.5, value));\n\
vec4 color = czm_antialias(evenColor, oddColor, currentColor, dist);\n\
material.diffuse = color.rgb;\n\
material.alpha = color.a;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/Water',[],function() {
'use strict';
return "uniform sampler2D specularMap;\n\
uniform sampler2D normalMap;\n\
uniform vec4 baseWaterColor;\n\
uniform vec4 blendColor;\n\
uniform float frequency;\n\
uniform float animationSpeed;\n\
uniform float amplitude;\n\
uniform float specularIntensity;\n\
uniform float fadeFactor;\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
float time = czm_frameNumber * animationSpeed;\n\
float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency * fadeFactor);\n\
float specularMapValue = texture2D(specularMap, materialInput.st).r;\n\
vec4 noise = czm_getWaterNoise(normalMap, materialInput.st * frequency, time, 0.0);\n\
vec3 normalTangentSpace = noise.xyz * vec3(1.0, 1.0, (1.0 / amplitude));\n\
normalTangentSpace.xy /= fade;\n\
normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);\n\
normalTangentSpace = normalize(normalTangentSpace);\n\
float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);\n\
material.alpha = specularMapValue;\n\
material.diffuse = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);\n\
material.diffuse += (0.1 * tsPerturbationRatio);\n\
material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);\n\
material.specular = specularIntensity;\n\
material.shininess = 10.0;\n\
return material;\n\
}\n\
";
});
/*global define*/
define('Scene/Material',[
'../Core/Cartesian2',
'../Core/clone',
'../Core/Color',
'../Core/combine',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/isArray',
'../Core/loadImage',
'../Core/Matrix2',
'../Core/Matrix3',
'../Core/Matrix4',
'../Renderer/CubeMap',
'../Renderer/Texture',
'../Shaders/Materials/BumpMapMaterial',
'../Shaders/Materials/CheckerboardMaterial',
'../Shaders/Materials/DotMaterial',
'../Shaders/Materials/FadeMaterial',
'../Shaders/Materials/GridMaterial',
'../Shaders/Materials/NormalMapMaterial',
'../Shaders/Materials/PolylineArrowMaterial',
'../Shaders/Materials/PolylineGlowMaterial',
'../Shaders/Materials/PolylineOutlineMaterial',
'../Shaders/Materials/RimLightingMaterial',
'../Shaders/Materials/StripeMaterial',
'../Shaders/Materials/Water',
'../ThirdParty/when'
], function(
Cartesian2,
clone,
Color,
combine,
createGuid,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
isArray,
loadImage,
Matrix2,
Matrix3,
Matrix4,
CubeMap,
Texture,
BumpMapMaterial,
CheckerboardMaterial,
DotMaterial,
FadeMaterial,
GridMaterial,
NormalMapMaterial,
PolylineArrowMaterial,
PolylineGlowMaterial,
PolylineOutlineMaterial,
RimLightingMaterial,
StripeMaterial,
WaterMaterial,
when) {
'use strict';
/**
* A Material defines surface appearance through a combination of diffuse, specular,
* normal, emission, and alpha components. These values are specified using a
* JSON schema called Fabric which gets parsed and assembled into glsl shader code
* behind-the-scenes. Check out the {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|wiki page}
* for more details on Fabric.
*
*
*
* Base material types and their uniforms:
*
*
* Color
*
* color
: rgba color object.
*
* Image
*
* image
: path to image.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
*
* DiffuseMap
*
* image
: path to image.
* channels
: Three character string containing any combination of r, g, b, and a for selecting the desired image channels.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
*
* AlphaMap
*
* image
: path to image.
* channel
: One character string containing r, g, b, or a for selecting the desired image channel.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
*
* SpecularMap
*
* image
: path to image.
* channel
: One character string containing r, g, b, or a for selecting the desired image channel.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
*
* EmissionMap
*
* image
: path to image.
* channels
: Three character string containing any combination of r, g, b, and a for selecting the desired image channels.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
*
* BumpMap
*
* image
: path to image.
* channel
: One character string containing r, g, b, or a for selecting the desired image channel.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
* strength
: Bump strength value between 0.0 and 1.0 where 0.0 is small bumps and 1.0 is large bumps.
*
* NormalMap
*
* image
: path to image.
* channels
: Three character string containing any combination of r, g, b, and a for selecting the desired image channels.
* repeat
: Object with x and y values specifying the number of times to repeat the image.
* strength
: Bump strength value between 0.0 and 1.0 where 0.0 is small bumps and 1.0 is large bumps.
*
* Grid
*
* color
: rgba color object for the whole material.
* cellAlpha
: Alpha value for the cells between grid lines. This will be combined with color.alpha.
* lineCount
: Object with x and y values specifying the number of columns and rows respectively.
* lineThickness
: Object with x and y values specifying the thickness of grid lines (in pixels where available).
* lineOffset
: Object with x and y values specifying the offset of grid lines (range is 0 to 1).
*
* Stripe
*
* horizontal
: Boolean that determines if the stripes are horizontal or vertical.
* evenColor
: rgba color object for the stripe's first color.
* oddColor
: rgba color object for the stripe's second color.
* offset
: Number that controls at which point into the pattern to begin drawing; with 0.0 being the beginning of the even color, 1.0 the beginning of the odd color, 2.0 being the even color again, and any multiple or fractional values being in between.
* repeat
: Number that controls the total number of stripes, half light and half dark.
*
* Checkerboard
*
* lightColor
: rgba color object for the checkerboard's light alternating color.
* darkColor
: rgba color object for the checkerboard's dark alternating color.
* repeat
: Object with x and y values specifying the number of columns and rows respectively.
*
* Dot
*
* lightColor
: rgba color object for the dot color.
* darkColor
: rgba color object for the background color.
* repeat
: Object with x and y values specifying the number of columns and rows of dots respectively.
*
* Water
*
* baseWaterColor
: rgba color object base color of the water.
* blendColor
: rgba color object used when blending from water to non-water areas.
* specularMap
: Single channel texture used to indicate areas of water.
* normalMap
: Normal map for water normal perturbation.
* frequency
: Number that controls the number of waves.
* normalMap
: Normal map for water normal perturbation.
* animationSpeed
: Number that controls the animations speed of the water.
* amplitude
: Number that controls the amplitude of water waves.
* specularIntensity
: Number that controls the intensity of specular reflections.
*
* RimLighting
*
* color
: diffuse color and alpha.
* rimColor
: diffuse color and alpha of the rim.
* width
: Number that determines the rim's width.
*
* Fade
*
* fadeInColor
: diffuse color and alpha at time
* fadeOutColor
: diffuse color and alpha at maximumDistance
from time
* maximumDistance
: Number between 0.0 and 1.0 where the fadeInColor
becomes the fadeOutColor
. A value of 0.0 gives the entire material a color of fadeOutColor
and a value of 1.0 gives the the entire material a color of fadeInColor
* repeat
: true if the fade should wrap around the texture coodinates.
* fadeDirection
: Object with x and y values specifying if the fade should be in the x and y directions.
* time
: Object with x and y values between 0.0 and 1.0 of the fadeInColor
position
*
* PolylineArrow
*
* color
: diffuse color and alpha.
*
* PolylineGlow
*
* color
: color and maximum alpha for the glow on the line.
* glowPower
: strength of the glow, as a percentage of the total line width (less than 1.0).
*
* PolylineOutline
*
* color
: diffuse color and alpha for the interior of the line.
* outlineColor
: diffuse color and alpha for the outline.
* outlineWidth
: width of the outline in pixels.
*
*
*
*
* @alias Material
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.strict=false] Throws errors for issues that would normally be ignored, including unused uniforms or materials.
* @param {Boolean|Function} [options.translucent=true] When true
or a function that returns true
, the geometry
* with this material is expected to appear translucent.
* @param {Object} options.fabric The fabric JSON used to generate the material.
*
* @constructor
*
* @exception {DeveloperError} fabric: uniform has invalid type.
* @exception {DeveloperError} fabric: uniforms and materials cannot share the same property.
* @exception {DeveloperError} fabric: cannot have source and components in the same section.
* @exception {DeveloperError} fabric: property name is not valid. It should be 'type', 'materials', 'uniforms', 'components', or 'source'.
* @exception {DeveloperError} fabric: property name is not valid. It should be 'diffuse', 'specular', 'shininess', 'normal', 'emission', or 'alpha'.
* @exception {DeveloperError} strict: shader source does not use string.
* @exception {DeveloperError} strict: shader source does not use uniform.
* @exception {DeveloperError} strict: shader source does not use material.
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric wiki page} for a more detailed options of Fabric.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Materials.html|Cesium Sandcastle Materials Demo}
*
* @example
* // Create a color material with fromType:
* polygon.material = Cesium.Material.fromType('Color');
* polygon.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
*
* // Create the default material:
* polygon.material = new Cesium.Material();
*
* // Create a color material with full Fabric notation:
* polygon.material = new Cesium.Material({
* fabric : {
* type : 'Color',
* uniforms : {
* color : new Cesium.Color(1.0, 1.0, 0.0, 1.0)
* }
* }
* });
*/
function Material(options) {
/**
* The material type. Can be an existing type or a new type. If no type is specified in fabric, type is a GUID.
* @type {String}
* @default undefined
*/
this.type = undefined;
/**
* The glsl shader source for this material.
* @type {String}
* @default undefined
*/
this.shaderSource = undefined;
/**
* Maps sub-material names to Material objects.
* @type {Object}
* @default undefined
*/
this.materials = undefined;
/**
* Maps uniform names to their values.
* @type {Object}
* @default undefined
*/
this.uniforms = undefined;
this._uniforms = undefined;
/**
* When true
or a function that returns true
,
* the geometry is expected to appear translucent.
* @type {Boolean|Function}
* @default undefined
*/
this.translucent = undefined;
this._strict = undefined;
this._template = undefined;
this._count = undefined;
this._texturePaths = {};
this._loadedImages = [];
this._loadedCubeMaps = [];
this._textures = {};
this._updateFunctions = [];
this._defaultTexture = undefined;
initializeMaterial(options, this);
defineProperties(this, {
type : {
value : this.type,
writable : false
}
});
if (!defined(Material._uniformList[this.type])) {
Material._uniformList[this.type] = Object.keys(this._uniforms);
}
}
// Cached list of combined uniform names indexed by type.
// Used to get the list of uniforms in the same order.
Material._uniformList = {};
/**
* Creates a new material using an existing material type.
*
* Shorthand for: new Material({fabric : {type : type}});
*
* @param {String} type The base material type.
* @param {Object} [uniforms] Overrides for the default uniforms.
* @returns {Material} New material object.
*
* @exception {DeveloperError} material with that type does not exist.
*
* @example
* var material = Cesium.Material.fromType('Color', {
* color : new Cesium.Color(1.0, 0.0, 0.0, 1.0)
* });
*/
Material.fromType = function(type, uniforms) {
if (!defined(Material._materialCache.getMaterial(type))) {
throw new DeveloperError('material with type \'' + type + '\' does not exist.');
}
var material = new Material({
fabric : {
type : type
}
});
if (defined(uniforms)) {
for (var name in uniforms) {
if (uniforms.hasOwnProperty(name)) {
material.uniforms[name] = uniforms[name];
}
}
}
return material;
};
/**
* Gets whether or not this material is translucent.
* @returns true
if this material is translucent, false
otherwise.
*/
Material.prototype.isTranslucent = function() {
if (defined(this.translucent)) {
if (typeof this.translucent === 'function') {
return this.translucent();
}
return this.translucent;
}
var translucent = true;
var funcs = this._translucentFunctions;
var length = funcs.length;
for (var i = 0; i < length; ++i) {
var func = funcs[i];
if (typeof func === 'function') {
translucent = translucent && func();
} else {
translucent = translucent && func;
}
if (!translucent) {
break;
}
}
return translucent;
};
/**
* @private
*/
Material.prototype.update = function(context) {
var i;
var uniformId;
var loadedImages = this._loadedImages;
var length = loadedImages.length;
for (i = 0; i < length; ++i) {
var loadedImage = loadedImages[i];
uniformId = loadedImage.id;
var image = loadedImage.image;
var texture = new Texture({
context : context,
source : image
});
this._textures[uniformId] = texture;
var uniformDimensionsName = uniformId + 'Dimensions';
if (this.uniforms.hasOwnProperty(uniformDimensionsName)) {
var uniformDimensions = this.uniforms[uniformDimensionsName];
uniformDimensions.x = texture._width;
uniformDimensions.y = texture._height;
}
}
loadedImages.length = 0;
var loadedCubeMaps = this._loadedCubeMaps;
length = loadedCubeMaps.length;
for (i = 0; i < length; ++i) {
var loadedCubeMap = loadedCubeMaps[i];
uniformId = loadedCubeMap.id;
var images = loadedCubeMap.images;
var cubeMap = new CubeMap({
context : context,
source : {
positiveX : images[0],
negativeX : images[1],
positiveY : images[2],
negativeY : images[3],
positiveZ : images[4],
negativeZ : images[5]
}
});
this._textures[uniformId] = cubeMap;
}
loadedCubeMaps.length = 0;
var updateFunctions = this._updateFunctions;
length = updateFunctions.length;
for (i = 0; i < length; ++i) {
updateFunctions[i](this, context);
}
var subMaterials = this.materials;
for (var name in subMaterials) {
if (subMaterials.hasOwnProperty(name)) {
subMaterials[name].update(context);
}
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see Material#destroy
*/
Material.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* material = material && material.destroy();
*
* @see Material#isDestroyed
*/
Material.prototype.destroy = function() {
var textures = this._textures;
for ( var texture in textures) {
if (textures.hasOwnProperty(texture)) {
var instance = textures[texture];
if (instance !== this._defaultTexture) {
instance.destroy();
}
}
}
var materials = this.materials;
for ( var material in materials) {
if (materials.hasOwnProperty(material)) {
materials[material].destroy();
}
}
return destroyObject(this);
};
function initializeMaterial(options, result) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
result._strict = defaultValue(options.strict, false);
result._count = defaultValue(options.count, 0);
result._template = clone(defaultValue(options.fabric, defaultValue.EMPTY_OBJECT));
result._template.uniforms = clone(defaultValue(result._template.uniforms, defaultValue.EMPTY_OBJECT));
result._template.materials = clone(defaultValue(result._template.materials, defaultValue.EMPTY_OBJECT));
result.type = defined(result._template.type) ? result._template.type : createGuid();
result.shaderSource = '';
result.materials = {};
result.uniforms = {};
result._uniforms = {};
result._translucentFunctions = [];
var translucent;
// If the cache contains this material type, build the material template off of the stored template.
var cachedMaterial = Material._materialCache.getMaterial(result.type);
if (defined(cachedMaterial)) {
var template = clone(cachedMaterial.fabric, true);
result._template = combine(result._template, template, true);
translucent = cachedMaterial.translucent;
}
// Make sure the template has no obvious errors. More error checking happens later.
checkForTemplateErrors(result);
// If the material has a new type, add it to the cache.
if (!defined(cachedMaterial)) {
Material._materialCache.addMaterial(result.type, result);
}
createMethodDefinition(result);
createUniforms(result);
createSubMaterials(result);
var defaultTranslucent = result._translucentFunctions.length === 0 ? true : undefined;
translucent = defaultValue(translucent, defaultTranslucent);
translucent = defaultValue(options.translucent, translucent);
if (defined(translucent)) {
if (typeof translucent === 'function') {
var wrappedTranslucent = function() {
return translucent(result);
};
result._translucentFunctions.push(wrappedTranslucent);
} else {
result._translucentFunctions.push(translucent);
}
}
}
function checkForValidProperties(object, properties, result, throwNotFound) {
if (defined(object)) {
for ( var property in object) {
if (object.hasOwnProperty(property)) {
var hasProperty = properties.indexOf(property) !== -1;
if ((throwNotFound && !hasProperty) || (!throwNotFound && hasProperty)) {
result(property, properties);
}
}
}
}
}
function invalidNameError(property, properties) {
var errorString = 'fabric: property name \'' + property + '\' is not valid. It should be ';
for ( var i = 0; i < properties.length; i++) {
var propertyName = '\'' + properties[i] + '\'';
errorString += (i === properties.length - 1) ? ('or ' + propertyName + '.') : (propertyName + ', ');
}
throw new DeveloperError(errorString);
}
function duplicateNameError(property, properties) {
var errorString = 'fabric: uniforms and materials cannot share the same property \'' + property + '\'';
throw new DeveloperError(errorString);
}
var templateProperties = ['type', 'materials', 'uniforms', 'components', 'source'];
var componentProperties = ['diffuse', 'specular', 'shininess', 'normal', 'emission', 'alpha'];
function checkForTemplateErrors(material) {
var template = material._template;
var uniforms = template.uniforms;
var materials = template.materials;
var components = template.components;
// Make sure source and components do not exist in the same template.
if (defined(components) && defined(template.source)) {
throw new DeveloperError('fabric: cannot have source and components in the same template.');
}
// Make sure all template and components properties are valid.
checkForValidProperties(template, templateProperties, invalidNameError, true);
checkForValidProperties(components, componentProperties, invalidNameError, true);
// Make sure uniforms and materials do not share any of the same names.
var materialNames = [];
for ( var property in materials) {
if (materials.hasOwnProperty(property)) {
materialNames.push(property);
}
}
checkForValidProperties(uniforms, materialNames, duplicateNameError, false);
}
// Create the czm_getMaterial method body using source or components.
function createMethodDefinition(material) {
var components = material._template.components;
var source = material._template.source;
if (defined(source)) {
material.shaderSource += source + '\n';
} else {
material.shaderSource += 'czm_material czm_getMaterial(czm_materialInput materialInput)\n{\n';
material.shaderSource += 'czm_material material = czm_getDefaultMaterial(materialInput);\n';
if (defined(components)) {
for ( var component in components) {
if (components.hasOwnProperty(component)) {
material.shaderSource += 'material.' + component + ' = ' + components[component] + ';\n';
}
}
}
material.shaderSource += 'return material;\n}\n';
}
}
var matrixMap = {
'mat2' : Matrix2,
'mat3' : Matrix3,
'mat4' : Matrix4
};
function createTexture2DUpdateFunction(uniformId) {
var oldUniformValue;
return function(material, context) {
var uniforms = material.uniforms;
var uniformValue = uniforms[uniformId];
var uniformChanged = oldUniformValue !== uniformValue;
oldUniformValue = uniformValue;
var texture = material._textures[uniformId];
var uniformDimensionsName;
var uniformDimensions;
if (uniformValue instanceof HTMLVideoElement) {
// HTMLVideoElement.readyState >=2 means we have enough data for the current frame.
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
if (uniformValue.readyState >= 2) {
if (uniformChanged && defined(texture)) {
if (texture !== context.defaultTexture) {
texture.destroy();
}
texture = undefined;
}
if (!defined(texture) || texture === context.defaultTexture) {
texture = new Texture({
context : context,
source : uniformValue
});
material._textures[uniformId] = texture;
return;
}
texture.copyFrom(uniformValue);
} else if (!defined(texture)) {
material._textures[uniformId] = context.defaultTexture;
}
return;
}
if (uniformValue instanceof Texture && uniformValue !== texture) {
material._texturePaths[uniformId] = undefined;
var tmp = material._textures[uniformId];
if (tmp !== material._defaultTexture) {
tmp.destroy();
}
material._textures[uniformId] = uniformValue;
uniformDimensionsName = uniformId + 'Dimensions';
if (uniforms.hasOwnProperty(uniformDimensionsName)) {
uniformDimensions = uniforms[uniformDimensionsName];
uniformDimensions.x = uniformValue._width;
uniformDimensions.y = uniformValue._height;
}
return;
}
if (!defined(texture)) {
material._texturePaths[uniformId] = undefined;
if (!defined(material._defaultTexture)) {
material._defaultTexture = context.defaultTexture;
}
texture = material._textures[uniformId] = material._defaultTexture;
uniformDimensionsName = uniformId + 'Dimensions';
if (uniforms.hasOwnProperty(uniformDimensionsName)) {
uniformDimensions = uniforms[uniformDimensionsName];
uniformDimensions.x = texture._width;
uniformDimensions.y = texture._height;
}
}
if (uniformValue === Material.DefaultImageId) {
return;
}
if (uniformValue !== material._texturePaths[uniformId]) {
if (typeof uniformValue === 'string') {
when(loadImage(uniformValue), function(image) {
material._loadedImages.push({
id : uniformId,
image : image
});
});
} else if (uniformValue instanceof HTMLCanvasElement) {
material._loadedImages.push({
id : uniformId,
image : uniformValue
});
}
material._texturePaths[uniformId] = uniformValue;
}
};
}
function createCubeMapUpdateFunction(uniformId) {
return function(material, context) {
var uniformValue = material.uniforms[uniformId];
if (uniformValue instanceof CubeMap) {
var tmp = material._textures[uniformId];
if (tmp !== material._defaultTexture) {
tmp.destroy();
}
material._texturePaths[uniformId] = undefined;
material._textures[uniformId] = uniformValue;
return;
}
if (!defined(material._textures[uniformId])) {
material._texturePaths[uniformId] = undefined;
material._textures[uniformId] = context.defaultCubeMap;
}
if (uniformValue === Material.DefaultCubeMapId) {
return;
}
var path =
uniformValue.positiveX + uniformValue.negativeX +
uniformValue.positiveY + uniformValue.negativeY +
uniformValue.positiveZ + uniformValue.negativeZ;
if (path !== material._texturePaths[uniformId]) {
var promises = [
loadImage(uniformValue.positiveX),
loadImage(uniformValue.negativeX),
loadImage(uniformValue.positiveY),
loadImage(uniformValue.negativeY),
loadImage(uniformValue.positiveZ),
loadImage(uniformValue.negativeZ)
];
when.all(promises).then(function(images) {
material._loadedCubeMaps.push({
id : uniformId,
images : images
});
});
material._texturePaths[uniformId] = path;
}
};
}
function createUniforms(material) {
var uniforms = material._template.uniforms;
for ( var uniformId in uniforms) {
if (uniforms.hasOwnProperty(uniformId)) {
createUniform(material, uniformId);
}
}
}
// Writes uniform declarations to the shader file and connects uniform values with
// corresponding material properties through the returnUniforms function.
function createUniform(material, uniformId) {
var strict = material._strict;
var materialUniforms = material._template.uniforms;
var uniformValue = materialUniforms[uniformId];
var uniformType = getUniformType(uniformValue);
if (!defined(uniformType)) {
throw new DeveloperError('fabric: uniform \'' + uniformId + '\' has invalid type.');
}
var replacedTokenCount;
if (uniformType === 'channels') {
replacedTokenCount = replaceToken(material, uniformId, uniformValue, false);
if (replacedTokenCount === 0 && strict) {
throw new DeveloperError('strict: shader source does not use channels \'' + uniformId + '\'.');
}
} else {
// Since webgl doesn't allow texture dimension queries in glsl, create a uniform to do it.
// Check if the shader source actually uses texture dimensions before creating the uniform.
if (uniformType === 'sampler2D') {
var imageDimensionsUniformName = uniformId + 'Dimensions';
if (getNumberOfTokens(material, imageDimensionsUniformName) > 0) {
materialUniforms[imageDimensionsUniformName] = {
type : 'ivec3',
x : 1,
y : 1
};
createUniform(material, imageDimensionsUniformName);
}
}
// Add uniform declaration to source code.
var uniformDeclarationRegex = new RegExp('uniform\\s+' + uniformType + '\\s+' + uniformId + '\\s*;');
if (!uniformDeclarationRegex.test(material.shaderSource)) {
var uniformDeclaration = 'uniform ' + uniformType + ' ' + uniformId + ';';
material.shaderSource = uniformDeclaration + material.shaderSource;
}
var newUniformId = uniformId + '_' + material._count++;
replacedTokenCount = replaceToken(material, uniformId, newUniformId);
if (replacedTokenCount === 1 && strict) {
throw new DeveloperError('strict: shader source does not use uniform \'' + uniformId + '\'.');
}
// Set uniform value
material.uniforms[uniformId] = uniformValue;
if (uniformType === 'sampler2D') {
material._uniforms[newUniformId] = function() {
return material._textures[uniformId];
};
material._updateFunctions.push(createTexture2DUpdateFunction(uniformId));
} else if (uniformType === 'samplerCube') {
material._uniforms[newUniformId] = function() {
return material._textures[uniformId];
};
material._updateFunctions.push(createCubeMapUpdateFunction(uniformId));
} else if (uniformType.indexOf('mat') !== -1) {
var scratchMatrix = new matrixMap[uniformType]();
material._uniforms[newUniformId] = function() {
return matrixMap[uniformType].fromColumnMajorArray(material.uniforms[uniformId], scratchMatrix);
};
} else {
material._uniforms[newUniformId] = function() {
return material.uniforms[uniformId];
};
}
}
}
// Determines the uniform type based on the uniform in the template.
function getUniformType(uniformValue) {
var uniformType = uniformValue.type;
if (!defined(uniformType)) {
var type = typeof uniformValue;
if (type === 'number') {
uniformType = 'float';
} else if (type === 'boolean') {
uniformType = 'bool';
} else if (type === 'string' || uniformValue instanceof HTMLCanvasElement) {
if (/^([rgba]){1,4}$/i.test(uniformValue)) {
uniformType = 'channels';
} else if (uniformValue === Material.DefaultCubeMapId) {
uniformType = 'samplerCube';
} else {
uniformType = 'sampler2D';
}
} else if (type === 'object') {
if (isArray(uniformValue)) {
if (uniformValue.length === 4 || uniformValue.length === 9 || uniformValue.length === 16) {
uniformType = 'mat' + Math.sqrt(uniformValue.length);
}
} else {
var numAttributes = 0;
for ( var attribute in uniformValue) {
if (uniformValue.hasOwnProperty(attribute)) {
numAttributes += 1;
}
}
if (numAttributes >= 2 && numAttributes <= 4) {
uniformType = 'vec' + numAttributes;
} else if (numAttributes === 6) {
uniformType = 'samplerCube';
}
}
}
}
return uniformType;
}
// Create all sub-materials by combining source and uniforms together.
function createSubMaterials(material) {
var strict = material._strict;
var subMaterialTemplates = material._template.materials;
for ( var subMaterialId in subMaterialTemplates) {
if (subMaterialTemplates.hasOwnProperty(subMaterialId)) {
// Construct the sub-material.
var subMaterial = new Material({
strict : strict,
fabric : subMaterialTemplates[subMaterialId],
count : material._count
});
material._count = subMaterial._count;
material._uniforms = combine(material._uniforms, subMaterial._uniforms, true);
material.materials[subMaterialId] = subMaterial;
material._translucentFunctions = material._translucentFunctions.concat(subMaterial._translucentFunctions);
// Make the material's czm_getMaterial unique by appending the sub-material type.
var originalMethodName = 'czm_getMaterial';
var newMethodName = originalMethodName + '_' + material._count++;
replaceToken(subMaterial, originalMethodName, newMethodName);
material.shaderSource = subMaterial.shaderSource + material.shaderSource;
// Replace each material id with an czm_getMaterial method call.
var materialMethodCall = newMethodName + '(materialInput)';
var tokensReplacedCount = replaceToken(material, subMaterialId, materialMethodCall);
if (tokensReplacedCount === 0 && strict) {
throw new DeveloperError('strict: shader source does not use material \'' + subMaterialId + '\'.');
}
}
}
}
// Used for searching or replacing a token in a material's shader source with something else.
// If excludePeriod is true, do not accept tokens that are preceded by periods.
// http://stackoverflow.com/questions/641407/javascript-negative-lookbehind-equivalent
function replaceToken(material, token, newToken, excludePeriod) {
excludePeriod = defaultValue(excludePeriod, true);
var count = 0;
var suffixChars = '([\\w])?';
var prefixChars = '([\\w' + (excludePeriod ? '.' : '') + '])?';
var regExp = new RegExp(prefixChars + token + suffixChars, 'g');
material.shaderSource = material.shaderSource.replace(regExp, function($0, $1, $2) {
if ($1 || $2) {
return $0;
}
count += 1;
return newToken;
});
return count;
}
function getNumberOfTokens(material, token, excludePeriod) {
return replaceToken(material, token, token, excludePeriod);
}
Material._materialCache = {
_materials : {},
addMaterial : function(type, materialTemplate) {
this._materials[type] = materialTemplate;
},
getMaterial : function(type) {
return this._materials[type];
}
};
/**
* Gets or sets the default texture uniform value.
* @type {String}
*/
Material.DefaultImageId = 'czm_defaultImage';
/**
* Gets or sets the default cube map texture uniform value.
* @type {String}
*/
Material.DefaultCubeMapId = 'czm_defaultCubeMap';
/**
* Gets the name of the color material.
* @type {String}
* @readonly
*/
Material.ColorType = 'Color';
Material._materialCache.addMaterial(Material.ColorType, {
fabric : {
type : Material.ColorType,
uniforms : {
color : new Color(1.0, 0.0, 0.0, 0.5)
},
components : {
diffuse : 'color.rgb',
alpha : 'color.a'
}
},
translucent : function(material) {
return material.uniforms.color.alpha < 1.0;
}
});
/**
* Gets the name of the image material.
* @type {String}
* @readonly
*/
Material.ImageType = 'Image';
Material._materialCache.addMaterial(Material.ImageType, {
fabric : {
type : Material.ImageType,
uniforms : {
image : Material.DefaultImageId,
repeat : new Cartesian2(1.0, 1.0),
color: new Color(1.0, 1.0, 1.0, 1.0)
},
components : {
diffuse : 'texture2D(image, fract(repeat * materialInput.st)).rgb * color.rgb',
alpha : 'texture2D(image, fract(repeat * materialInput.st)).a * color.a'
}
},
translucent : function(material) {
return material.uniforms.color.alpha < 1.0;
}
});
/**
* Gets the name of the diffuce map material.
* @type {String}
* @readonly
*/
Material.DiffuseMapType = 'DiffuseMap';
Material._materialCache.addMaterial(Material.DiffuseMapType, {
fabric : {
type : Material.DiffuseMapType,
uniforms : {
image : Material.DefaultImageId,
channels : 'rgb',
repeat : new Cartesian2(1.0, 1.0)
},
components : {
diffuse : 'texture2D(image, fract(repeat * materialInput.st)).channels'
}
},
translucent : false
});
/**
* Gets the name of the alpha map material.
* @type {String}
* @readonly
*/
Material.AlphaMapType = 'AlphaMap';
Material._materialCache.addMaterial(Material.AlphaMapType, {
fabric : {
type : Material.AlphaMapType,
uniforms : {
image : Material.DefaultImageId,
channel : 'a',
repeat : new Cartesian2(1.0, 1.0)
},
components : {
alpha : 'texture2D(image, fract(repeat * materialInput.st)).channel'
}
},
translucent : true
});
/**
* Gets the name of the specular map material.
* @type {String}
* @readonly
*/
Material.SpecularMapType = 'SpecularMap';
Material._materialCache.addMaterial(Material.SpecularMapType, {
fabric : {
type : Material.SpecularMapType,
uniforms : {
image : Material.DefaultImageId,
channel : 'r',
repeat : new Cartesian2(1.0, 1.0)
},
components : {
specular : 'texture2D(image, fract(repeat * materialInput.st)).channel'
}
},
translucent : false
});
/**
* Gets the name of the emmision map material.
* @type {String}
* @readonly
*/
Material.EmissionMapType = 'EmissionMap';
Material._materialCache.addMaterial(Material.EmissionMapType, {
fabric : {
type : Material.EmissionMapType,
uniforms : {
image : Material.DefaultImageId,
channels : 'rgb',
repeat : new Cartesian2(1.0, 1.0)
},
components : {
emission : 'texture2D(image, fract(repeat * materialInput.st)).channels'
}
},
translucent : false
});
/**
* Gets the name of the bump map material.
* @type {String}
* @readonly
*/
Material.BumpMapType = 'BumpMap';
Material._materialCache.addMaterial(Material.BumpMapType, {
fabric : {
type : Material.BumpMapType,
uniforms : {
image : Material.DefaultImageId,
channel : 'r',
strength : 0.8,
repeat : new Cartesian2(1.0, 1.0)
},
source : BumpMapMaterial
},
translucent : false
});
/**
* Gets the name of the normal map material.
* @type {String}
* @readonly
*/
Material.NormalMapType = 'NormalMap';
Material._materialCache.addMaterial(Material.NormalMapType, {
fabric : {
type : Material.NormalMapType,
uniforms : {
image : Material.DefaultImageId,
channels : 'rgb',
strength : 0.8,
repeat : new Cartesian2(1.0, 1.0)
},
source : NormalMapMaterial
},
translucent : false
});
/**
* Gets the name of the grid material.
* @type {String}
* @readonly
*/
Material.GridType = 'Grid';
Material._materialCache.addMaterial(Material.GridType, {
fabric : {
type : Material.GridType,
uniforms : {
color : new Color(0.0, 1.0, 0.0, 1.0),
cellAlpha : 0.1,
lineCount : new Cartesian2(8.0, 8.0),
lineThickness : new Cartesian2(1.0, 1.0),
lineOffset : new Cartesian2(0.0, 0.0)
},
source : GridMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.color.alpha < 1.0) || (uniforms.cellAlpha < 1.0);
}
});
/**
* Gets the name of the stripe material.
* @type {String}
* @readonly
*/
Material.StripeType = 'Stripe';
Material._materialCache.addMaterial(Material.StripeType, {
fabric : {
type : Material.StripeType,
uniforms : {
horizontal : true,
evenColor : new Color(1.0, 1.0, 1.0, 0.5),
oddColor : new Color(0.0, 0.0, 1.0, 0.5),
offset : 0.0,
repeat : 5.0
},
source : StripeMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.evenColor.alpha < 1.0) || (uniforms.oddColor.alpha < 0.0);
}
});
/**
* Gets the name of the checkerboard material.
* @type {String}
* @readonly
*/
Material.CheckerboardType = 'Checkerboard';
Material._materialCache.addMaterial(Material.CheckerboardType, {
fabric : {
type : Material.CheckerboardType,
uniforms : {
lightColor : new Color(1.0, 1.0, 1.0, 0.5),
darkColor : new Color(0.0, 0.0, 0.0, 0.5),
repeat : new Cartesian2(5.0, 5.0)
},
source : CheckerboardMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.lightColor.alpha < 1.0) || (uniforms.darkColor.alpha < 0.0);
}
});
/**
* Gets the name of the dot material.
* @type {String}
* @readonly
*/
Material.DotType = 'Dot';
Material._materialCache.addMaterial(Material.DotType, {
fabric : {
type : Material.DotType,
uniforms : {
lightColor : new Color(1.0, 1.0, 0.0, 0.75),
darkColor : new Color(0.0, 1.0, 1.0, 0.75),
repeat : new Cartesian2(5.0, 5.0)
},
source : DotMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.lightColor.alpha < 1.0) || (uniforms.darkColor.alpha < 0.0);
}
});
/**
* Gets the name of the water material.
* @type {String}
* @readonly
*/
Material.WaterType = 'Water';
Material._materialCache.addMaterial(Material.WaterType, {
fabric : {
type : Material.WaterType,
uniforms : {
baseWaterColor : new Color(0.2, 0.3, 0.6, 1.0),
blendColor : new Color(0.0, 1.0, 0.699, 1.0),
specularMap : Material.DefaultImageId,
normalMap : Material.DefaultImageId,
frequency : 10.0,
animationSpeed : 0.01,
amplitude : 1.0,
specularIntensity : 0.5,
fadeFactor : 1.0
},
source : WaterMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.baseWaterColor.alpha < 1.0) || (uniforms.blendColor.alpha < 0.0);
}
});
/**
* Gets the name of the rim lighting material.
* @type {String}
* @readonly
*/
Material.RimLightingType = 'RimLighting';
Material._materialCache.addMaterial(Material.RimLightingType, {
fabric : {
type : Material.RimLightingType,
uniforms : {
color : new Color(1.0, 0.0, 0.0, 0.7),
rimColor : new Color(1.0, 1.0, 1.0, 0.4),
width : 0.3
},
source : RimLightingMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.color.alpha < 1.0) || (uniforms.rimColor.alpha < 0.0);
}
});
/**
* Gets the name of the fade material.
* @type {String}
* @readonly
*/
Material.FadeType = 'Fade';
Material._materialCache.addMaterial(Material.FadeType, {
fabric : {
type : Material.FadeType,
uniforms : {
fadeInColor : new Color(1.0, 0.0, 0.0, 1.0),
fadeOutColor : new Color(0.0, 0.0, 0.0, 0.0),
maximumDistance : 0.5,
repeat : true,
fadeDirection : {
x : true,
y : true
},
time : new Cartesian2(0.5, 0.5)
},
source : FadeMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.fadeInColor.alpha < 1.0) || (uniforms.fadeOutColor.alpha < 0.0);
}
});
/**
* Gets the name of the polyline arrow material.
* @type {String}
* @readonly
*/
Material.PolylineArrowType = 'PolylineArrow';
Material._materialCache.addMaterial(Material.PolylineArrowType, {
fabric : {
type : Material.PolylineArrowType,
uniforms : {
color : new Color(1.0, 1.0, 1.0, 1.0)
},
source : PolylineArrowMaterial
},
translucent : true
});
/**
* Gets the name of the polyline glow material.
* @type {String}
* @readonly
*/
Material.PolylineGlowType = 'PolylineGlow';
Material._materialCache.addMaterial(Material.PolylineGlowType, {
fabric : {
type : Material.PolylineGlowType,
uniforms : {
color : new Color(0.0, 0.5, 1.0, 1.0),
glowPower : 0.25
},
source : PolylineGlowMaterial
},
translucent : true
});
/**
* Gets the name of the polyline outline material.
* @type {String}
* @readonly
*/
Material.PolylineOutlineType = 'PolylineOutline';
Material._materialCache.addMaterial(Material.PolylineOutlineType, {
fabric : {
type : Material.PolylineOutlineType,
uniforms : {
color : new Color(1.0, 1.0, 1.0, 1.0),
outlineColor : new Color(1.0, 0.0, 0.0, 1.0),
outlineWidth : 1.0
},
source : PolylineOutlineMaterial
},
translucent : function(material) {
var uniforms = material.uniforms;
return (uniforms.color.alpha < 1.0) || (uniforms.outlineColor.alpha < 1.0);
}
});
return Material;
});
/*global define*/
define('Scene/MaterialAppearance',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/freezeObject',
'../Core/VertexFormat',
'../Shaders/Appearances/AllMaterialAppearanceFS',
'../Shaders/Appearances/AllMaterialAppearanceVS',
'../Shaders/Appearances/BasicMaterialAppearanceFS',
'../Shaders/Appearances/BasicMaterialAppearanceVS',
'../Shaders/Appearances/TexturedMaterialAppearanceFS',
'../Shaders/Appearances/TexturedMaterialAppearanceVS',
'./Appearance',
'./Material'
], function(
defaultValue,
defined,
defineProperties,
freezeObject,
VertexFormat,
AllMaterialAppearanceFS,
AllMaterialAppearanceVS,
BasicMaterialAppearanceFS,
BasicMaterialAppearanceVS,
TexturedMaterialAppearanceFS,
TexturedMaterialAppearanceVS,
Appearance,
Material) {
'use strict';
/**
* An appearance for arbitrary geometry (as opposed to {@link EllipsoidSurfaceAppearance}, for example)
* that supports shading with materials.
*
* @alias MaterialAppearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.flat=false] When true
, flat shading is used in the fragment shader, which means lighting is not taking into account.
* @param {Boolean} [options.faceForward=!options.closed] When true
, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots. This is useful when both sides of a geometry should be shaded like {@link WallGeometry}.
* @param {Boolean} [options.translucent=true] When true
, the geometry is expected to appear translucent so {@link MaterialAppearance#renderState} has alpha blending enabled.
* @param {Boolean} [options.closed=false] When true
, the geometry is expected to be closed so {@link MaterialAppearance#renderState} has backface culling enabled.
* @param {MaterialAppearance.MaterialSupport} [options.materialSupport=MaterialAppearance.MaterialSupport.TEXTURED] The type of materials that will be supported.
* @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Materials.html|Cesium Sandcastle Material Appearance Demo}
*
* @example
* var primitive = new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : new Cesium.WallGeometry({
materialSupport : Cesium.MaterialAppearance.MaterialSupport.BASIC.vertexFormat,
* // ...
* })
* }),
* appearance : new Cesium.MaterialAppearance({
* material : Cesium.Material.fromType('Color'),
* faceForward : true
* })
*
* });
*/
function MaterialAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var translucent = defaultValue(options.translucent, true);
var closed = defaultValue(options.closed, false);
var materialSupport = defaultValue(options.materialSupport, MaterialAppearance.MaterialSupport.TEXTURED);
/**
* The material used to determine the fragment color. Unlike other {@link MaterialAppearance}
* properties, this is not read-only, so an appearance's material can change on the fly.
*
* @type Material
*
* @default {@link Material.ColorType}
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = (defined(options.material)) ? options.material : Material.fromType(Material.ColorType);
/**
* When true
, the geometry is expected to appear translucent.
*
* @type {Boolean}
*
* @default true
*/
this.translucent = translucent;
this._vertexShaderSource = defaultValue(options.vertexShaderSource, materialSupport.vertexShaderSource);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, materialSupport.fragmentShaderSource);
this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
this._closed = closed;
// Non-derived members
this._materialSupport = materialSupport;
this._vertexFormat = materialSupport.vertexFormat;
this._flat = defaultValue(options.flat, false);
this._faceForward = defaultValue(options.faceForward, !closed);
}
defineProperties(MaterialAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof MaterialAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader. The full fragment shader
* source is built procedurally taking into account {@link MaterialAppearance#material},
* {@link MaterialAppearance#flat}, and {@link MaterialAppearance#faceForward}.
* Use {@link MaterialAppearance#getFragmentShaderSource} to get the full source.
*
* @memberof MaterialAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* The render state can be explicitly defined when constructing a {@link MaterialAppearance}
* instance, or it is set implicitly via {@link MaterialAppearance#translucent}
* and {@link MaterialAppearance#closed}.
*
*
* @memberof MaterialAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed so
* {@link MaterialAppearance#renderState} has backface culling enabled.
* If the viewer enters the geometry, it will not be visible.
*
* @memberof MaterialAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
},
/**
* The type of materials supported by this instance. This impacts the required
* {@link VertexFormat} and the complexity of the vertex and fragment shaders.
*
* @memberof MaterialAppearance.prototype
*
* @type {MaterialAppearance.MaterialSupport}
* @readonly
*
* @default {@link MaterialAppearance.MaterialSupport.TEXTURED}
*/
materialSupport : {
get : function() {
return this._materialSupport;
}
},
/**
* The {@link VertexFormat} that this appearance instance is compatible with.
* A geometry can have more vertex attributes and still be compatible - at a
* potential performance cost - but it can't have less.
*
* @memberof MaterialAppearance.prototype
*
* @type VertexFormat
* @readonly
*
* @default {@link MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat}
*/
vertexFormat : {
get : function() {
return this._vertexFormat;
}
},
/**
* When true
, flat shading is used in the fragment shader,
* which means lighting is not taking into account.
*
* @memberof MaterialAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
flat : {
get : function() {
return this._flat;
}
},
/**
* When true
, the fragment shader flips the surface normal
* as needed to ensure that the normal faces the viewer to avoid
* dark spots. This is useful when both sides of a geometry should be
* shaded like {@link WallGeometry}.
*
* @memberof MaterialAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
faceForward : {
get : function() {
return this._faceForward;
}
}
});
/**
* Procedurally creates the full GLSL fragment shader source. For {@link MaterialAppearance},
* this is derived from {@link MaterialAppearance#fragmentShaderSource}, {@link MaterialAppearance#material},
* {@link MaterialAppearance#flat}, and {@link MaterialAppearance#faceForward}.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
MaterialAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link MaterialAppearance#translucent} and {@link Material#isTranslucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
MaterialAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
MaterialAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
/**
* Determines the type of {@link Material} that is supported by a
* {@link MaterialAppearance} instance. This is a trade-off between
* flexibility (a wide array of materials) and memory/performance
* (required vertex format and GLSL shader complexity.
*/
MaterialAppearance.MaterialSupport = {
/**
* Only basic materials, which require just position
and
* normal
vertex attributes, are supported.
*
* @constant
*/
BASIC : freezeObject({
vertexFormat : VertexFormat.POSITION_AND_NORMAL,
vertexShaderSource : BasicMaterialAppearanceVS,
fragmentShaderSource : BasicMaterialAppearanceFS
}),
/**
* Materials with textures, which require position
,
* normal
, and st
vertex attributes,
* are supported. The vast majority of materials fall into this category.
*
* @constant
*/
TEXTURED : freezeObject({
vertexFormat : VertexFormat.POSITION_NORMAL_AND_ST,
vertexShaderSource : TexturedMaterialAppearanceVS,
fragmentShaderSource : TexturedMaterialAppearanceFS
}),
/**
* All materials, including those that work in tangent space, are supported.
* This requires position
, normal
, st
,
* binormal
, and tangent
vertex attributes.
*
* @constant
*/
ALL : freezeObject({
vertexFormat : VertexFormat.ALL,
vertexShaderSource : AllMaterialAppearanceVS,
fragmentShaderSource : AllMaterialAppearanceFS
})
};
return MaterialAppearance;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceColorAppearanceFS',[],function() {
'use strict';
return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec4 v_color;\n\
void main()\n\
{\n\
vec3 positionToEyeEC = -v_positionEC;\n\
vec3 normalEC = normalize(v_normalEC);\n\
#ifdef FACE_FORWARD\n\
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
czm_materialInput materialInput;\n\
materialInput.normalEC = normalEC;\n\
materialInput.positionToEyeEC = positionToEyeEC;\n\
czm_material material = czm_getDefaultMaterial(materialInput);\n\
material.diffuse = v_color.rgb;\n\
material.alpha = v_color.a;\n\
gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceColorAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec4 v_color;\n\
void main()\n\
{\n\
vec4 p = czm_computePosition();\n\
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;\n\
v_normalEC = czm_normal * normal;\n\
v_color = color;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceFlatColorAppearanceFS',[],function() {
'use strict';
return "varying vec4 v_color;\n\
void main()\n\
{\n\
gl_FragColor = v_color;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceFlatColorAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
varying vec4 v_color;\n\
void main()\n\
{\n\
vec4 p = czm_computePosition();\n\
v_color = color;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/PerInstanceColorAppearance',[
'../Core/defaultValue',
'../Core/defineProperties',
'../Core/VertexFormat',
'../Shaders/Appearances/PerInstanceColorAppearanceFS',
'../Shaders/Appearances/PerInstanceColorAppearanceVS',
'../Shaders/Appearances/PerInstanceFlatColorAppearanceFS',
'../Shaders/Appearances/PerInstanceFlatColorAppearanceVS',
'./Appearance'
], function(
defaultValue,
defineProperties,
VertexFormat,
PerInstanceColorAppearanceFS,
PerInstanceColorAppearanceVS,
PerInstanceFlatColorAppearanceFS,
PerInstanceFlatColorAppearanceVS,
Appearance) {
'use strict';
/**
* An appearance for {@link GeometryInstance} instances with color attributes.
* This allows several geometry instances, each with a different color, to
* be drawn with the same {@link Primitive} as shown in the second example below.
*
* @alias PerInstanceColorAppearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.flat=false] When true
, flat shading is used in the fragment shader, which means lighting is not taking into account.
* @param {Boolean} [options.faceForward=!options.closed] When true
, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots. This is useful when both sides of a geometry should be shaded like {@link WallGeometry}.
* @param {Boolean} [options.translucent=true] When true
, the geometry is expected to appear translucent so {@link PerInstanceColorAppearance#renderState} has alpha blending enabled.
* @param {Boolean} [options.closed=false] When true
, the geometry is expected to be closed so {@link PerInstanceColorAppearance#renderState} has backface culling enabled.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @example
* // A solid white line segment
* var primitive = new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : new Cesium.SimplePolylineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 0.0, 0.0,
* 5.0, 0.0
* ])
* }),
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 1.0, 1.0, 1.0))
* }
* }),
* appearance : new Cesium.PerInstanceColorAppearance({
* flat : true,
* translucent : false
* })
* });
*
* // Two rectangles in a primitive, each with a different color
* var instance = new Cesium.GeometryInstance({
* geometry : new Cesium.RectangleGeometry({
* rectangle : Cesium.Rectangle.fromDegrees(0.0, 20.0, 10.0, 30.0)
* }),
* attributes : {
* color : new Cesium.Color(1.0, 0.0, 0.0, 0.5)
* }
* });
*
* var anotherInstance = new Cesium.GeometryInstance({
* geometry : new Cesium.RectangleGeometry({
* rectangle : Cesium.Rectangle.fromDegrees(0.0, 40.0, 10.0, 50.0)
* }),
* attributes : {
* color : new Cesium.Color(0.0, 0.0, 1.0, 0.5)
* }
* });
*
* var rectanglePrimitive = new Cesium.Primitive({
* geometryInstances : [instance, anotherInstance],
* appearance : new Cesium.PerInstanceColorAppearance()
* });
*/
function PerInstanceColorAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var translucent = defaultValue(options.translucent, true);
var closed = defaultValue(options.closed, false);
var flat = defaultValue(options.flat, false);
var vs = flat ? PerInstanceFlatColorAppearanceVS : PerInstanceColorAppearanceVS;
var fs = flat ? PerInstanceFlatColorAppearanceFS : PerInstanceColorAppearanceFS;
var vertexFormat = flat ? PerInstanceColorAppearance.FLAT_VERTEX_FORMAT : PerInstanceColorAppearance.VERTEX_FORMAT;
/**
* This property is part of the {@link Appearance} interface, but is not
* used by {@link PerInstanceColorAppearance} since a fully custom fragment shader is used.
*
* @type Material
*
* @default undefined
*/
this.material = undefined;
/**
* When true
, the geometry is expected to appear translucent so
* {@link PerInstanceColorAppearance#renderState} has alpha blending enabled.
*
* @type {Boolean}
*
* @default true
*/
this.translucent = translucent;
this._vertexShaderSource = defaultValue(options.vertexShaderSource, vs);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs);
this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
this._closed = closed;
// Non-derived members
this._vertexFormat = vertexFormat;
this._flat = flat;
this._faceForward = defaultValue(options.faceForward, !closed);
}
defineProperties(PerInstanceColorAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader.
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* The render state can be explicitly defined when constructing a {@link PerInstanceColorAppearance}
* instance, or it is set implicitly via {@link PerInstanceColorAppearance#translucent}
* and {@link PerInstanceColorAppearance#closed}.
*
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed so
* {@link PerInstanceColorAppearance#renderState} has backface culling enabled.
* If the viewer enters the geometry, it will not be visible.
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
},
/**
* The {@link VertexFormat} that this appearance instance is compatible with.
* A geometry can have more vertex attributes and still be compatible - at a
* potential performance cost - but it can't have less.
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type VertexFormat
* @readonly
*/
vertexFormat : {
get : function() {
return this._vertexFormat;
}
},
/**
* When true
, flat shading is used in the fragment shader,
* which means lighting is not taking into account.
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
flat : {
get : function() {
return this._flat;
}
},
/**
* When true
, the fragment shader flips the surface normal
* as needed to ensure that the normal faces the viewer to avoid
* dark spots. This is useful when both sides of a geometry should be
* shaded like {@link WallGeometry}.
*
* @memberof PerInstanceColorAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
faceForward : {
get : function() {
return this._faceForward;
}
}
});
/**
* The {@link VertexFormat} that all {@link PerInstanceColorAppearance} instances
* are compatible with. This requires only position
and st
* attributes.
*
* @type VertexFormat
*
* @constant
*/
PerInstanceColorAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_NORMAL;
/**
* The {@link VertexFormat} that all {@link PerInstanceColorAppearance} instances
* are compatible with when {@link PerInstanceColorAppearance#flat} is false
.
* This requires only a position
attribute.
*
* @type VertexFormat
*
* @constant
*/
PerInstanceColorAppearance.FLAT_VERTEX_FORMAT = VertexFormat.POSITION_ONLY;
/**
* Procedurally creates the full GLSL fragment shader source. For {@link PerInstanceColorAppearance},
* this is derived from {@link PerInstanceColorAppearance#fragmentShaderSource}, {@link PerInstanceColorAppearance#flat},
* and {@link PerInstanceColorAppearance#faceForward}.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
PerInstanceColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link PerInstanceColorAppearance#translucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
PerInstanceColorAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
PerInstanceColorAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
return PerInstanceColorAppearance;
});
/*global define*/
define('Renderer/BufferUsage',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var BufferUsage = {
STREAM_DRAW : WebGLConstants.STREAM_DRAW,
STATIC_DRAW : WebGLConstants.STATIC_DRAW,
DYNAMIC_DRAW : WebGLConstants.DYNAMIC_DRAW,
validate : function(bufferUsage) {
return ((bufferUsage === BufferUsage.STREAM_DRAW) ||
(bufferUsage === BufferUsage.STATIC_DRAW) ||
(bufferUsage === BufferUsage.DYNAMIC_DRAW));
}
};
return freezeObject(BufferUsage);
});
/*global define*/
define('Renderer/DrawCommand',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/PrimitiveType'
], function(
defaultValue,
defined,
defineProperties,
PrimitiveType) {
'use strict';
/**
* Represents a command to the renderer for drawing.
*
* @private
*/
function DrawCommand(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._boundingVolume = options.boundingVolume;
this._orientedBoundingBox = options.orientedBoundingBox;
this._cull = defaultValue(options.cull, true);
this._modelMatrix = options.modelMatrix;
this._primitiveType = defaultValue(options.primitiveType, PrimitiveType.TRIANGLES);
this._vertexArray = options.vertexArray;
this._count = options.count;
this._offset = defaultValue(options.offset, 0);
this._instanceCount = defaultValue(options.instanceCount, 0);
this._shaderProgram = options.shaderProgram;
this._uniformMap = options.uniformMap;
this._renderState = options.renderState;
this._framebuffer = options.framebuffer;
this._pass = options.pass;
this._executeInClosestFrustum = defaultValue(options.executeInClosestFrustum, false);
this._owner = options.owner;
this._debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
this._debugOverlappingFrustums = 0;
this._castShadows = defaultValue(options.castShadows, false);
this._receiveShadows = defaultValue(options.receiveShadows, false);
this.dirty = true;
this.lastDirtyTime = 0;
/**
* @private
*/
this.derivedCommands = {};
}
defineProperties(DrawCommand.prototype, {
/**
* The bounding volume of the geometry in world space. This is used for culling and frustum selection.
*
* For best rendering performance, use the tightest possible bounding volume. Although
* undefined
is allowed, always try to provide a bounding volume to
* allow the tightest possible near and far planes to be computed for the scene, and
* minimize the number of frustums needed.
*
*
* @memberof DrawCommand.prototype
* @type {Object}
* @default undefined
*
* @see DrawCommand#debugShowBoundingVolume
*/
boundingVolume : {
get : function() {
return this._boundingVolume;
},
set : function(value) {
if (this._boundingVolume !== value) {
this._boundingVolume = value;
this.dirty = true;
}
}
},
/**
* The oriented bounding box of the geometry in world space. If this is defined, it is used instead of
* {@link DrawCommand#boundingVolume} for plane intersection testing.
*
* @memberof DrawCommand.prototype
* @type {OrientedBoundingBox}
* @default undefined
*
* @see DrawCommand#debugShowBoundingVolume
*/
orientedBoundingBox : {
get : function() {
return this._orientedBoundingBox;
},
set : function(value) {
if (this._orientedBoundingBox !== value) {
this._orientedBoundingBox = value;
this.dirty = true;
}
}
},
/**
* When true
, the renderer frustum and horizon culls the command based on its {@link DrawCommand#boundingVolume}.
* If the command was already culled, set this to false
for a performance improvement.
*
* @memberof DrawCommand.prototype
* @type {Boolean}
* @default true
*/
cull : {
get : function() {
return this._cull;
},
set : function(value) {
if (this._cull !== value) {
this._cull = value;
this.dirty = true;
}
}
},
/**
* The transformation from the geometry in model space to world space.
*
* When undefined
, the geometry is assumed to be defined in world space.
*
*
* @memberof DrawCommand.prototype
* @type {Matrix4}
* @default undefined
*/
modelMatrix : {
get : function() {
return this._modelMatrix;
},
set : function(value) {
if (this._modelMatrix !== value) {
this._modelMatrix = value;
this.dirty = true;
}
}
},
/**
* The type of geometry in the vertex array.
*
* @memberof DrawCommand.prototype
* @type {PrimitiveType}
* @default PrimitiveType.TRIANGLES
*/
primitiveType : {
get : function() {
return this._primitiveType;
},
set : function(value) {
if (this._primitiveType !== value) {
this._primitiveType = value;
this.dirty = true;
}
}
},
/**
* The vertex array.
*
* @memberof DrawCommand.prototype
* @type {VertexArray}
* @default undefined
*/
vertexArray : {
get : function() {
return this._vertexArray;
},
set : function(value) {
if (this._vertexArray !== value) {
this._vertexArray = value;
this.dirty = true;
}
}
},
/**
* The number of vertices to draw in the vertex array.
*
* @memberof DrawCommand.prototype
* @type {Number}
* @default undefined
*/
count : {
get : function() {
return this._count;
},
set : function(value) {
if (this._count !== value) {
this._count = value;
this.dirty = true;
}
}
},
/**
* The offset to start drawing in the vertex array.
*
* @memberof DrawCommand.prototype
* @type {Number}
* @default 0
*/
offset : {
get : function() {
return this._offset;
},
set : function(value) {
if (this._offset !== value) {
this._offset = value;
this.dirty = true;
}
}
},
/**
* The number of instances to draw.
*
* @memberof DrawCommand.prototype
* @type {Number}
* @default 0
*/
instanceCount : {
get : function() {
return this._instanceCount;
},
set : function(value) {
if (this._instanceCount !== value) {
this._instanceCount = value;
this.dirty = true;
}
}
},
/**
* The shader program to apply.
*
* @memberof DrawCommand.prototype
* @type {ShaderProgram}
* @default undefined
*/
shaderProgram : {
get : function() {
return this._shaderProgram;
},
set : function(value) {
if (this._shaderProgram !== value) {
this._shaderProgram = value;
this.dirty = true;
}
}
},
/**
* Whether this command should cast shadows when shadowing is enabled.
*
* @memberof DrawCommand.prototype
* @type {Boolean}
* @default false
*/
castShadows : {
get : function() {
return this._castShadows;
},
set : function(value) {
if (this._castShadows !== value) {
this._castShadows = value;
this.dirty = true;
}
}
},
/**
* Whether this command should receive shadows when shadowing is enabled.
*
* @memberof DrawCommand.prototype
* @type {Boolean}
* @default false
*/
receiveShadows : {
get : function() {
return this._receiveShadows;
},
set : function(value) {
if (this._receiveShadows !== value) {
this._receiveShadows = value;
this.dirty = true;
}
}
},
/**
* An object with functions whose names match the uniforms in the shader program
* and return values to set those uniforms.
*
* @memberof DrawCommand.prototype
* @type {Object}
* @default undefined
*/
uniformMap : {
get : function() {
return this._uniformMap;
},
set : function(value) {
if (this._uniformMap !== value) {
this._uniformMap = value;
this.dirty = true;
}
}
},
/**
* The render state.
*
* @memberof DrawCommand.prototype
* @type {RenderState}
* @default undefined
*/
renderState : {
get : function() {
return this._renderState;
},
set : function(value) {
if (this._renderState !== value) {
this._renderState = value;
this.dirty = true;
}
}
},
/**
* The framebuffer to draw to.
*
* @memberof DrawCommand.prototype
* @type {Framebuffer}
* @default undefined
*/
framebuffer : {
get : function() {
return this._framebuffer;
},
set : function(value) {
if (this._framebuffer !== value) {
this._framebuffer = value;
this.dirty = true;
}
}
},
/**
* The pass when to render.
*
* @memberof DrawCommand.prototype
* @type {Pass}
* @default undefined
*/
pass : {
get : function() {
return this._pass;
},
set : function(value) {
if (this._pass !== value) {
this._pass = value;
this.dirty = true;
}
}
},
/**
* Specifies if this command is only to be executed in the frustum closest
* to the eye containing the bounding volume. Defaults to false
.
*
* @memberof DrawCommand.prototype
* @type {Boolean}
* @default false
*/
executeInClosestFrustum : {
get : function() {
return this._executeInClosestFrustum;
},
set : function(value) {
if (this._executeInClosestFrustum !== value) {
this._executeInClosestFrustum = value;
this.dirty = true;
}
}
},
/**
* The object who created this command. This is useful for debugging command
* execution; it allows us to see who created a command when we only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @memberof DrawCommand.prototype
* @type {Object}
* @default undefined
*
* @see Scene#debugCommandFilter
*/
owner : {
get : function() {
return this._owner;
},
set : function(value) {
if (this._owner !== value) {
this._owner = value;
this.dirty = true;
}
}
},
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the {@link DrawCommand#boundingVolume} for this command, assuming it is a sphere, when the command executes.
*
*
* @memberof DrawCommand.prototype
* @type {Boolean}
* @default false
*
* @see DrawCommand#boundingVolume
*/
debugShowBoundingVolume : {
get : function() {
return this._debugShowBoundingVolume;
},
set : function(value) {
if (this._debugShowBoundingVolume !== value) {
this._debugShowBoundingVolume = value;
this.dirty = true;
}
}
},
/**
* Used to implement Scene.debugShowFrustums.
* @private
*/
debugOverlappingFrustums : {
get : function() {
return this._debugOverlappingFrustums;
},
set : function(value) {
if (this._debugOverlappingFrustums !== value) {
this._debugOverlappingFrustums = value;
this.dirty = true;
}
}
}
});
/**
* @private
*/
DrawCommand.shallowClone = function(command, result) {
if (!defined(command)) {
return undefined;
}
if (!defined(result)) {
result = new DrawCommand();
}
result._boundingVolume = command._boundingVolume;
result._orientedBoundingBox = command._orientedBoundingBox;
result._cull = command._cull;
result._modelMatrix = command._modelMatrix;
result._primitiveType = command._primitiveType;
result._vertexArray = command._vertexArray;
result._count = command._count;
result._offset = command._offset;
result._instanceCount = command._instanceCount;
result._shaderProgram = command._shaderProgram;
result._uniformMap = command._uniformMap;
result._renderState = command._renderState;
result._framebuffer = command._framebuffer;
result._pass = command._pass;
result._executeInClosestFrustum = command._executeInClosestFrustum;
result._owner = command._owner;
result._debugShowBoundingVolume = command._debugShowBoundingVolume;
result._debugOverlappingFrustums = command._debugOverlappingFrustums;
result._castShadows = command._castShadows;
result._receiveShadows = command._receiveShadows;
result.dirty = true;
result.lastDirtyTime = 0;
return result;
};
/**
* Executes the draw command.
*
* @param {Context} context The renderer context in which to draw.
* @param {PassState} [passState] The state for the current render pass.
*/
DrawCommand.prototype.execute = function(context, passState) {
context.draw(this, passState);
};
return DrawCommand;
});
/*global define*/
define('Renderer/Pass',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* The render pass for a command.
*
* @private
*/
var Pass = {
// If you add/modify/remove Pass constants, also change the automatic GLSL constants
// that start with 'czm_pass'
//
// Commands are executed in order by pass up to the translucent pass.
// Translucent geometry needs special handling (sorting/OIT). The compute pass
// is executed first and the overlay pass is executed last. Both are not sorted
// by frustum.
ENVIRONMENT : 0,
COMPUTE : 1,
GLOBE : 2,
GROUND : 3,
OPAQUE : 4,
TRANSLUCENT : 5,
OVERLAY : 6,
NUMBER_OF_PASSES : 7
};
return freezeObject(Pass);
});
/*global define*/
define('Renderer/RenderState',[
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/WebGLConstants',
'../Core/WindingOrder',
'./ContextLimits'
], function(
BoundingRectangle,
Color,
defaultValue,
defined,
DeveloperError,
WebGLConstants,
WindingOrder,
ContextLimits) {
'use strict';
function validateBlendEquation(blendEquation) {
return ((blendEquation === WebGLConstants.FUNC_ADD) ||
(blendEquation === WebGLConstants.FUNC_SUBTRACT) ||
(blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT));
}
function validateBlendFunction(blendFunction) {
return ((blendFunction === WebGLConstants.ZERO) ||
(blendFunction === WebGLConstants.ONE) ||
(blendFunction === WebGLConstants.SRC_COLOR) ||
(blendFunction === WebGLConstants.ONE_MINUS_SRC_COLOR) ||
(blendFunction === WebGLConstants.DST_COLOR) ||
(blendFunction === WebGLConstants.ONE_MINUS_DST_COLOR) ||
(blendFunction === WebGLConstants.SRC_ALPHA) ||
(blendFunction === WebGLConstants.ONE_MINUS_SRC_ALPHA) ||
(blendFunction === WebGLConstants.DST_ALPHA) ||
(blendFunction === WebGLConstants.ONE_MINUS_DST_ALPHA) ||
(blendFunction === WebGLConstants.CONSTANT_COLOR) ||
(blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_COLOR) ||
(blendFunction === WebGLConstants.CONSTANT_ALPHA) ||
(blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_ALPHA) ||
(blendFunction === WebGLConstants.SRC_ALPHA_SATURATE));
}
function validateCullFace(cullFace) {
return ((cullFace === WebGLConstants.FRONT) ||
(cullFace === WebGLConstants.BACK) ||
(cullFace === WebGLConstants.FRONT_AND_BACK));
}
function validateDepthFunction(depthFunction) {
return ((depthFunction === WebGLConstants.NEVER) ||
(depthFunction === WebGLConstants.LESS) ||
(depthFunction === WebGLConstants.EQUAL) ||
(depthFunction === WebGLConstants.LEQUAL) ||
(depthFunction === WebGLConstants.GREATER) ||
(depthFunction === WebGLConstants.NOTEQUAL) ||
(depthFunction === WebGLConstants.GEQUAL) ||
(depthFunction === WebGLConstants.ALWAYS));
}
function validateStencilFunction (stencilFunction) {
return ((stencilFunction === WebGLConstants.NEVER) ||
(stencilFunction === WebGLConstants.LESS) ||
(stencilFunction === WebGLConstants.EQUAL) ||
(stencilFunction === WebGLConstants.LEQUAL) ||
(stencilFunction === WebGLConstants.GREATER) ||
(stencilFunction === WebGLConstants.NOTEQUAL) ||
(stencilFunction === WebGLConstants.GEQUAL) ||
(stencilFunction === WebGLConstants.ALWAYS));
}
function validateStencilOperation(stencilOperation) {
return ((stencilOperation === WebGLConstants.ZERO) ||
(stencilOperation === WebGLConstants.KEEP) ||
(stencilOperation === WebGLConstants.REPLACE) ||
(stencilOperation === WebGLConstants.INCR) ||
(stencilOperation === WebGLConstants.DECR) ||
(stencilOperation === WebGLConstants.INVERT) ||
(stencilOperation === WebGLConstants.INCR_WRAP) ||
(stencilOperation === WebGLConstants.DECR_WRAP));
}
/**
* @private
*/
function RenderState(renderState) {
var rs = defaultValue(renderState, {});
var cull = defaultValue(rs.cull, {});
var polygonOffset = defaultValue(rs.polygonOffset, {});
var scissorTest = defaultValue(rs.scissorTest, {});
var scissorTestRectangle = defaultValue(scissorTest.rectangle, {});
var depthRange = defaultValue(rs.depthRange, {});
var depthTest = defaultValue(rs.depthTest, {});
var colorMask = defaultValue(rs.colorMask, {});
var blending = defaultValue(rs.blending, {});
var blendingColor = defaultValue(blending.color, {});
var stencilTest = defaultValue(rs.stencilTest, {});
var stencilTestFrontOperation = defaultValue(stencilTest.frontOperation, {});
var stencilTestBackOperation = defaultValue(stencilTest.backOperation, {});
var sampleCoverage = defaultValue(rs.sampleCoverage, {});
var viewport = rs.viewport;
this.frontFace = defaultValue(rs.frontFace, WindingOrder.COUNTER_CLOCKWISE);
this.cull = {
enabled : defaultValue(cull.enabled, false),
face : defaultValue(cull.face, WebGLConstants.BACK)
};
this.lineWidth = defaultValue(rs.lineWidth, 1.0);
this.polygonOffset = {
enabled : defaultValue(polygonOffset.enabled, false),
factor : defaultValue(polygonOffset.factor, 0),
units : defaultValue(polygonOffset.units, 0)
};
this.scissorTest = {
enabled : defaultValue(scissorTest.enabled, false),
rectangle : BoundingRectangle.clone(scissorTestRectangle)
};
this.depthRange = {
near : defaultValue(depthRange.near, 0),
far : defaultValue(depthRange.far, 1)
};
this.depthTest = {
enabled : defaultValue(depthTest.enabled, false),
func : defaultValue(depthTest.func, WebGLConstants.LESS) // func, because function is a JavaScript keyword
};
this.colorMask = {
red : defaultValue(colorMask.red, true),
green : defaultValue(colorMask.green, true),
blue : defaultValue(colorMask.blue, true),
alpha : defaultValue(colorMask.alpha, true)
};
this.depthMask = defaultValue(rs.depthMask, true);
this.stencilMask = defaultValue(rs.stencilMask, ~0);
this.blending = {
enabled : defaultValue(blending.enabled, false),
color : new Color(
defaultValue(blendingColor.red, 0.0),
defaultValue(blendingColor.green, 0.0),
defaultValue(blendingColor.blue, 0.0),
defaultValue(blendingColor.alpha, 0.0)
),
equationRgb : defaultValue(blending.equationRgb, WebGLConstants.FUNC_ADD),
equationAlpha : defaultValue(blending.equationAlpha, WebGLConstants.FUNC_ADD),
functionSourceRgb : defaultValue(blending.functionSourceRgb, WebGLConstants.ONE),
functionSourceAlpha : defaultValue(blending.functionSourceAlpha, WebGLConstants.ONE),
functionDestinationRgb : defaultValue(blending.functionDestinationRgb, WebGLConstants.ZERO),
functionDestinationAlpha : defaultValue(blending.functionDestinationAlpha, WebGLConstants.ZERO)
};
this.stencilTest = {
enabled : defaultValue(stencilTest.enabled, false),
frontFunction : defaultValue(stencilTest.frontFunction, WebGLConstants.ALWAYS),
backFunction : defaultValue(stencilTest.backFunction, WebGLConstants.ALWAYS),
reference : defaultValue(stencilTest.reference, 0),
mask : defaultValue(stencilTest.mask, ~0),
frontOperation : {
fail : defaultValue(stencilTestFrontOperation.fail, WebGLConstants.KEEP),
zFail : defaultValue(stencilTestFrontOperation.zFail, WebGLConstants.KEEP),
zPass : defaultValue(stencilTestFrontOperation.zPass, WebGLConstants.KEEP)
},
backOperation : {
fail : defaultValue(stencilTestBackOperation.fail, WebGLConstants.KEEP),
zFail : defaultValue(stencilTestBackOperation.zFail, WebGLConstants.KEEP),
zPass : defaultValue(stencilTestBackOperation.zPass, WebGLConstants.KEEP)
}
};
this.sampleCoverage = {
enabled : defaultValue(sampleCoverage.enabled, false),
value : defaultValue(sampleCoverage.value, 1.0),
invert : defaultValue(sampleCoverage.invert, false)
};
this.viewport = (defined(viewport)) ? new BoundingRectangle(viewport.x, viewport.y, viewport.width, viewport.height) : undefined;
if ((this.lineWidth < ContextLimits.minimumAliasedLineWidth) ||
(this.lineWidth > ContextLimits.maximumAliasedLineWidth)) {
throw new DeveloperError('renderState.lineWidth is out of range. Check minimumAliasedLineWidth and maximumAliasedLineWidth.');
}
if (!WindingOrder.validate(this.frontFace)) {
throw new DeveloperError('Invalid renderState.frontFace.');
}
if (!validateCullFace(this.cull.face)) {
throw new DeveloperError('Invalid renderState.cull.face.');
}
if ((this.scissorTest.rectangle.width < 0) ||
(this.scissorTest.rectangle.height < 0)) {
throw new DeveloperError('renderState.scissorTest.rectangle.width and renderState.scissorTest.rectangle.height must be greater than or equal to zero.');
}
if (this.depthRange.near > this.depthRange.far) {
// WebGL specific - not an error in GL ES
throw new DeveloperError('renderState.depthRange.near can not be greater than renderState.depthRange.far.');
}
if (this.depthRange.near < 0) {
// Would be clamped by GL
throw new DeveloperError('renderState.depthRange.near must be greater than or equal to zero.');
}
if (this.depthRange.far > 1) {
// Would be clamped by GL
throw new DeveloperError('renderState.depthRange.far must be less than or equal to one.');
}
if (!validateDepthFunction(this.depthTest.func)) {
throw new DeveloperError('Invalid renderState.depthTest.func.');
}
if ((this.blending.color.red < 0.0) || (this.blending.color.red > 1.0) ||
(this.blending.color.green < 0.0) || (this.blending.color.green > 1.0) ||
(this.blending.color.blue < 0.0) || (this.blending.color.blue > 1.0) ||
(this.blending.color.alpha < 0.0) || (this.blending.color.alpha > 1.0)) {
// Would be clamped by GL
throw new DeveloperError('renderState.blending.color components must be greater than or equal to zero and less than or equal to one.');
}
if (!validateBlendEquation(this.blending.equationRgb)) {
throw new DeveloperError('Invalid renderState.blending.equationRgb.');
}
if (!validateBlendEquation(this.blending.equationAlpha)) {
throw new DeveloperError('Invalid renderState.blending.equationAlpha.');
}
if (!validateBlendFunction(this.blending.functionSourceRgb)) {
throw new DeveloperError('Invalid renderState.blending.functionSourceRgb.');
}
if (!validateBlendFunction(this.blending.functionSourceAlpha)) {
throw new DeveloperError('Invalid renderState.blending.functionSourceAlpha.');
}
if (!validateBlendFunction(this.blending.functionDestinationRgb)) {
throw new DeveloperError('Invalid renderState.blending.functionDestinationRgb.');
}
if (!validateBlendFunction(this.blending.functionDestinationAlpha)) {
throw new DeveloperError('Invalid renderState.blending.functionDestinationAlpha.');
}
if (!validateStencilFunction(this.stencilTest.frontFunction)) {
throw new DeveloperError('Invalid renderState.stencilTest.frontFunction.');
}
if (!validateStencilFunction(this.stencilTest.backFunction)) {
throw new DeveloperError('Invalid renderState.stencilTest.backFunction.');
}
if (!validateStencilOperation(this.stencilTest.frontOperation.fail)) {
throw new DeveloperError('Invalid renderState.stencilTest.frontOperation.fail.');
}
if (!validateStencilOperation(this.stencilTest.frontOperation.zFail)) {
throw new DeveloperError('Invalid renderState.stencilTest.frontOperation.zFail.');
}
if (!validateStencilOperation(this.stencilTest.frontOperation.zPass)) {
throw new DeveloperError('Invalid renderState.stencilTest.frontOperation.zPass.');
}
if (!validateStencilOperation(this.stencilTest.backOperation.fail)) {
throw new DeveloperError('Invalid renderState.stencilTest.backOperation.fail.');
}
if (!validateStencilOperation(this.stencilTest.backOperation.zFail)) {
throw new DeveloperError('Invalid renderState.stencilTest.backOperation.zFail.');
}
if (!validateStencilOperation(this.stencilTest.backOperation.zPass)) {
throw new DeveloperError('Invalid renderState.stencilTest.backOperation.zPass.');
}
if (defined(this.viewport)) {
if (this.viewport.width < 0) {
throw new DeveloperError('renderState.viewport.width must be greater than or equal to zero.');
}
if (this.viewport.height < 0) {
throw new DeveloperError('renderState.viewport.height must be greater than or equal to zero.');
}
if (this.viewport.width > ContextLimits.maximumViewportWidth) {
throw new DeveloperError('renderState.viewport.width must be less than or equal to the maximum viewport width (' + ContextLimits.maximumViewportWidth.toString() + '). Check maximumViewportWidth.');
}
if (this.viewport.height > ContextLimits.maximumViewportHeight) {
throw new DeveloperError('renderState.viewport.height must be less than or equal to the maximum viewport height (' + ContextLimits.maximumViewportHeight.toString() + '). Check maximumViewportHeight.');
}
}
this.id = 0;
this._applyFunctions = [];
}
var nextRenderStateId = 0;
var renderStateCache = {};
/**
* Validates and then finds or creates an immutable render state, which defines the pipeline
* state for a {@link DrawCommand} or {@link ClearCommand}. All inputs states are optional. Omitted states
* use the defaults shown in the example below.
*
* @param {Object} [renderState] The states defining the render state as shown in the example below.
*
* @exception {RuntimeError} renderState.lineWidth is out of range.
* @exception {DeveloperError} Invalid renderState.frontFace.
* @exception {DeveloperError} Invalid renderState.cull.face.
* @exception {DeveloperError} scissorTest.rectangle.width and scissorTest.rectangle.height must be greater than or equal to zero.
* @exception {DeveloperError} renderState.depthRange.near can't be greater than renderState.depthRange.far.
* @exception {DeveloperError} renderState.depthRange.near must be greater than or equal to zero.
* @exception {DeveloperError} renderState.depthRange.far must be less than or equal to zero.
* @exception {DeveloperError} Invalid renderState.depthTest.func.
* @exception {DeveloperError} renderState.blending.color components must be greater than or equal to zero and less than or equal to one
* @exception {DeveloperError} Invalid renderState.blending.equationRgb.
* @exception {DeveloperError} Invalid renderState.blending.equationAlpha.
* @exception {DeveloperError} Invalid renderState.blending.functionSourceRgb.
* @exception {DeveloperError} Invalid renderState.blending.functionSourceAlpha.
* @exception {DeveloperError} Invalid renderState.blending.functionDestinationRgb.
* @exception {DeveloperError} Invalid renderState.blending.functionDestinationAlpha.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontFunction.
* @exception {DeveloperError} Invalid renderState.stencilTest.backFunction.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.fail.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zFail.
* @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zPass.
* @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.fail.
* @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zFail.
* @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zPass.
* @exception {DeveloperError} renderState.viewport.width must be greater than or equal to zero.
* @exception {DeveloperError} renderState.viewport.width must be less than or equal to the maximum viewport width.
* @exception {DeveloperError} renderState.viewport.height must be greater than or equal to zero.
* @exception {DeveloperError} renderState.viewport.height must be less than or equal to the maximum viewport height.
*
*
* @example
* var defaults = {
* frontFace : WindingOrder.COUNTER_CLOCKWISE,
* cull : {
* enabled : false,
* face : CullFace.BACK
* },
* lineWidth : 1,
* polygonOffset : {
* enabled : false,
* factor : 0,
* units : 0
* },
* scissorTest : {
* enabled : false,
* rectangle : {
* x : 0,
* y : 0,
* width : 0,
* height : 0
* }
* },
* depthRange : {
* near : 0,
* far : 1
* },
* depthTest : {
* enabled : false,
* func : DepthFunction.LESS
* },
* colorMask : {
* red : true,
* green : true,
* blue : true,
* alpha : true
* },
* depthMask : true,
* stencilMask : ~0,
* blending : {
* enabled : false,
* color : {
* red : 0.0,
* green : 0.0,
* blue : 0.0,
* alpha : 0.0
* },
* equationRgb : BlendEquation.ADD,
* equationAlpha : BlendEquation.ADD,
* functionSourceRgb : BlendFunction.ONE,
* functionSourceAlpha : BlendFunction.ONE,
* functionDestinationRgb : BlendFunction.ZERO,
* functionDestinationAlpha : BlendFunction.ZERO
* },
* stencilTest : {
* enabled : false,
* frontFunction : StencilFunction.ALWAYS,
* backFunction : StencilFunction.ALWAYS,
* reference : 0,
* mask : ~0,
* frontOperation : {
* fail : StencilOperation.KEEP,
* zFail : StencilOperation.KEEP,
* zPass : StencilOperation.KEEP
* },
* backOperation : {
* fail : StencilOperation.KEEP,
* zFail : StencilOperation.KEEP,
* zPass : StencilOperation.KEEP
* }
* },
* sampleCoverage : {
* enabled : false,
* value : 1.0,
* invert : false
* }
* };
*
* var rs = RenderState.fromCache(defaults);
*
* @see DrawCommand
* @see ClearCommand
*
* @private
*/
RenderState.fromCache = function(renderState) {
var partialKey = JSON.stringify(renderState);
var cachedState = renderStateCache[partialKey];
if (defined(cachedState)) {
++cachedState.referenceCount;
return cachedState.state;
}
// Cache miss. Fully define render state and try again.
var states = new RenderState(renderState);
var fullKey = JSON.stringify(states);
cachedState = renderStateCache[fullKey];
if (!defined(cachedState)) {
states.id = nextRenderStateId++;
cachedState = {
referenceCount : 0,
state : states
};
// Cache full render state. Multiple partially defined render states may map to this.
renderStateCache[fullKey] = cachedState;
}
++cachedState.referenceCount;
// Cache partial render state so we can skip validation on a cache hit for a partially defined render state
renderStateCache[partialKey] = {
referenceCount : 1,
state : cachedState.state
};
return cachedState.state;
};
/**
* @private
*/
RenderState.removeFromCache = function(renderState) {
var states = new RenderState(renderState);
var fullKey = JSON.stringify(states);
var fullCachedState = renderStateCache[fullKey];
// decrement partial key reference count
var partialKey = JSON.stringify(renderState);
var cachedState = renderStateCache[partialKey];
if (defined(cachedState)) {
--cachedState.referenceCount;
if (cachedState.referenceCount === 0) {
// remove partial key
delete renderStateCache[partialKey];
// decrement full key reference count
if (defined(fullCachedState)) {
--fullCachedState.referenceCount;
}
}
}
// remove full key if reference count is zero
if (defined(fullCachedState) && (fullCachedState.referenceCount === 0)) {
delete renderStateCache[fullKey];
}
};
/**
* This function is for testing purposes only.
* @private
*/
RenderState.getCache = function() {
return renderStateCache;
};
/**
* This function is for testing purposes only.
* @private
*/
RenderState.clearCache = function() {
renderStateCache = {};
};
function enableOrDisable(gl, glEnum, enable) {
if (enable) {
gl.enable(glEnum);
} else {
gl.disable(glEnum);
}
}
function applyFrontFace(gl, renderState) {
gl.frontFace(renderState.frontFace);
}
function applyCull(gl, renderState) {
var cull = renderState.cull;
var enabled = cull.enabled;
enableOrDisable(gl, gl.CULL_FACE, enabled);
if (enabled) {
gl.cullFace(cull.face);
}
}
function applyLineWidth(gl, renderState) {
gl.lineWidth(renderState.lineWidth);
}
function applyPolygonOffset(gl, renderState) {
var polygonOffset = renderState.polygonOffset;
var enabled = polygonOffset.enabled;
enableOrDisable(gl, gl.POLYGON_OFFSET_FILL, enabled);
if (enabled) {
gl.polygonOffset(polygonOffset.factor, polygonOffset.units);
}
}
function applyScissorTest(gl, renderState, passState) {
var scissorTest = renderState.scissorTest;
var enabled = (defined(passState.scissorTest)) ? passState.scissorTest.enabled : scissorTest.enabled;
enableOrDisable(gl, gl.SCISSOR_TEST, enabled);
if (enabled) {
var rectangle = (defined(passState.scissorTest)) ? passState.scissorTest.rectangle : scissorTest.rectangle;
gl.scissor(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
}
function applyDepthRange(gl, renderState) {
var depthRange = renderState.depthRange;
gl.depthRange(depthRange.near, depthRange.far);
}
function applyDepthTest(gl, renderState) {
var depthTest = renderState.depthTest;
var enabled = depthTest.enabled;
enableOrDisable(gl, gl.DEPTH_TEST, enabled);
if (enabled) {
gl.depthFunc(depthTest.func);
}
}
function applyColorMask(gl, renderState) {
var colorMask = renderState.colorMask;
gl.colorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha);
}
function applyDepthMask(gl, renderState) {
gl.depthMask(renderState.depthMask);
}
function applyStencilMask(gl, renderState) {
gl.stencilMask(renderState.stencilMask);
}
function applyBlendingColor(gl, color) {
gl.blendColor(color.red, color.green, color.blue, color.alpha);
}
function applyBlending(gl, renderState, passState) {
var blending = renderState.blending;
var enabled = (defined(passState.blendingEnabled)) ? passState.blendingEnabled : blending.enabled;
enableOrDisable(gl, gl.BLEND, enabled);
if (enabled) {
applyBlendingColor(gl, blending.color);
gl.blendEquationSeparate(blending.equationRgb, blending.equationAlpha);
gl.blendFuncSeparate(blending.functionSourceRgb, blending.functionDestinationRgb, blending.functionSourceAlpha, blending.functionDestinationAlpha);
}
}
function applyStencilTest(gl, renderState) {
var stencilTest = renderState.stencilTest;
var enabled = stencilTest.enabled;
enableOrDisable(gl, gl.STENCIL_TEST, enabled);
if (enabled) {
var frontFunction = stencilTest.frontFunction;
var backFunction = stencilTest.backFunction;
var reference = stencilTest.reference;
var mask = stencilTest.mask;
// Section 6.8 of the WebGL spec requires the reference and masks to be the same for
// front- and back-face tests. This call prevents invalid operation errors when calling
// stencilFuncSeparate on Firefox. Perhaps they should delay validation to avoid requiring this.
gl.stencilFunc(frontFunction, reference, mask);
gl.stencilFuncSeparate(gl.BACK, backFunction, reference, mask);
gl.stencilFuncSeparate(gl.FRONT, frontFunction, reference, mask);
var frontOperation = stencilTest.frontOperation;
var frontOperationFail = frontOperation.fail;
var frontOperationZFail = frontOperation.zFail;
var frontOperationZPass = frontOperation.zPass;
gl.stencilOpSeparate(gl.FRONT, frontOperationFail, frontOperationZFail, frontOperationZPass);
var backOperation = stencilTest.backOperation;
var backOperationFail = backOperation.fail;
var backOperationZFail = backOperation.zFail;
var backOperationZPass = backOperation.zPass;
gl.stencilOpSeparate(gl.BACK, backOperationFail, backOperationZFail, backOperationZPass);
}
}
function applySampleCoverage(gl, renderState) {
var sampleCoverage = renderState.sampleCoverage;
var enabled = sampleCoverage.enabled;
enableOrDisable(gl, gl.SAMPLE_COVERAGE, enabled);
if (enabled) {
gl.sampleCoverage(sampleCoverage.value, sampleCoverage.invert);
}
}
var scratchViewport = new BoundingRectangle();
function applyViewport(gl, renderState, passState) {
var viewport = defaultValue(renderState.viewport, passState.viewport);
if (!defined(viewport)) {
viewport = scratchViewport;
viewport.width = passState.context.drawingBufferWidth;
viewport.height = passState.context.drawingBufferHeight;
}
passState.context.uniformState.viewport = viewport;
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
}
RenderState.apply = function(gl, renderState, passState) {
applyFrontFace(gl, renderState);
applyCull(gl, renderState);
applyLineWidth(gl, renderState);
applyPolygonOffset(gl, renderState);
applyDepthRange(gl, renderState);
applyDepthTest(gl, renderState);
applyColorMask(gl, renderState);
applyDepthMask(gl, renderState);
applyStencilMask(gl, renderState);
applyStencilTest(gl, renderState);
applySampleCoverage(gl, renderState);
applyScissorTest(gl, renderState, passState);
applyBlending(gl, renderState, passState);
applyViewport(gl, renderState, passState);
};
function createFuncs(previousState, nextState) {
var funcs = [];
if (previousState.frontFace !== nextState.frontFace) {
funcs.push(applyFrontFace);
}
if ((previousState.cull.enabled !== nextState.cull.enabled) || (previousState.cull.face !== nextState.cull.face)) {
funcs.push(applyCull);
}
if (previousState.lineWidth !== nextState.lineWidth) {
funcs.push(applyLineWidth);
}
if ((previousState.polygonOffset.enabled !== nextState.polygonOffset.enabled) ||
(previousState.polygonOffset.factor !== nextState.polygonOffset.factor) ||
(previousState.polygonOffset.units !== nextState.polygonOffset.units)) {
funcs.push(applyPolygonOffset);
}
if ((previousState.depthRange.near !== nextState.depthRange.near) || (previousState.depthRange.far !== nextState.depthRange.far)) {
funcs.push(applyDepthRange);
}
if ((previousState.depthTest.enabled !== nextState.depthTest.enabled) || (previousState.depthTest.func !== nextState.depthTest.func)) {
funcs.push(applyDepthTest);
}
if ((previousState.colorMask.red !== nextState.colorMask.red) ||
(previousState.colorMask.green !== nextState.colorMask.green) ||
(previousState.colorMask.blue !== nextState.colorMask.blue) ||
(previousState.colorMask.alpha !== nextState.colorMask.alpha)) {
funcs.push(applyColorMask);
}
if (previousState.depthMask !== nextState.depthMask) {
funcs.push(applyDepthMask);
}
if (previousState.stencilMask !== nextState.stencilMask) {
funcs.push(applyStencilMask);
}
if ((previousState.stencilTest.enabled !== nextState.stencilTest.enabled) ||
(previousState.stencilTest.frontFunction !== nextState.stencilTest.frontFunction) ||
(previousState.stencilTest.backFunction !== nextState.stencilTest.backFunction) ||
(previousState.stencilTest.reference !== nextState.stencilTest.reference) ||
(previousState.stencilTest.mask !== nextState.stencilTest.mask) ||
(previousState.stencilTest.frontOperation.fail !== nextState.stencilTest.frontOperation.fail) ||
(previousState.stencilTest.frontOperation.zFail !== nextState.stencilTest.frontOperation.zFail) ||
(previousState.stencilTest.backOperation.fail !== nextState.stencilTest.backOperation.fail) ||
(previousState.stencilTest.backOperation.zFail !== nextState.stencilTest.backOperation.zFail) ||
(previousState.stencilTest.backOperation.zPass !== nextState.stencilTest.backOperation.zPass)) {
funcs.push(applyStencilTest);
}
if ((previousState.sampleCoverage.enabled !== nextState.sampleCoverage.enabled) ||
(previousState.sampleCoverage.value !== nextState.sampleCoverage.value) ||
(previousState.sampleCoverage.invert !== nextState.sampleCoverage.invert)) {
funcs.push(applySampleCoverage);
}
return funcs;
}
RenderState.partialApply = function(gl, previousRenderState, renderState, previousPassState, passState, clear) {
if (previousRenderState !== renderState) {
// When a new render state is applied, instead of making WebGL calls for all the states or first
// comparing the states one-by-one with the previous state (basically a linear search), we take
// advantage of RenderState's immutability, and store a dynamically populated sparse data structure
// containing functions that make the minimum number of WebGL calls when transitioning from one state
// to the other. In practice, this works well since state-to-state transitions generally only require a
// few WebGL calls, especially if commands are stored by state.
var funcs = renderState._applyFunctions[previousRenderState.id];
if (!defined(funcs)) {
funcs = createFuncs(previousRenderState, renderState);
renderState._applyFunctions[previousRenderState.id] = funcs;
}
var len = funcs.length;
for (var i = 0; i < len; ++i) {
funcs[i](gl, renderState);
}
}
var previousScissorTest = (defined(previousPassState.scissorTest)) ? previousPassState.scissorTest : previousRenderState.scissorTest;
var scissorTest = (defined(passState.scissorTest)) ? passState.scissorTest : renderState.scissorTest;
// Our scissor rectangle can get out of sync with the GL scissor rectangle on clears.
// Seems to be a problem only on ANGLE. See https://github.com/AnalyticalGraphicsInc/cesium/issues/2994
if ((previousScissorTest !== scissorTest) || clear) {
applyScissorTest(gl, renderState, passState);
}
var previousBlendingEnabled = (defined(previousPassState.blendingEnabled)) ? previousPassState.blendingEnabled : previousRenderState.blending.enabled;
var blendingEnabled = (defined(passState.blendingEnabled)) ? passState.blendingEnabled : renderState.blending.enabled;
if ((previousBlendingEnabled !== blendingEnabled) ||
(blendingEnabled && (previousRenderState.blending !== renderState.blending))) {
applyBlending(gl, renderState, passState);
}
if (previousRenderState !== renderState || previousPassState !== passState || previousPassState.context !== passState.context) {
applyViewport(gl, renderState, passState);
}
};
RenderState.getState = function(renderState) {
if (!defined(renderState)) {
throw new DeveloperError('renderState is required.');
}
return {
frontFace : renderState.frontFace,
cull : {
enabled : renderState.cull.enabled,
face : renderState.cull.face
},
lineWidth : renderState.lineWidth,
polygonOffset : {
enabled : renderState.polygonOffset.enabled,
factor : renderState.polygonOffset.factor,
units : renderState.polygonOffset.units
},
scissorTest : {
enabled : renderState.scissorTest.enabled,
rectangle : BoundingRectangle.clone(renderState.scissorTest.rectangle)
},
depthRange : {
near : renderState.depthRange.near,
far : renderState.depthRange.far
},
depthTest : {
enabled : renderState.depthTest.enabled,
func : renderState.depthTest.func
},
colorMask : {
red : renderState.colorMask.red,
green : renderState.colorMask.green,
blue : renderState.colorMask.blue,
alpha : renderState.colorMask.alpha
},
depthMask : renderState.depthMask,
stencilMask : renderState.stencilMask,
blending : {
enabled : renderState.blending.enabled,
color : Color.clone(renderState.blending.color),
equationRgb : renderState.blending.equationRgb,
equationAlpha : renderState.blending.equationAlpha,
functionSourceRgb : renderState.blending.functionSourceRgb,
functionSourceAlpha : renderState.blending.functionSourceAlpha,
functionDestinationRgb : renderState.blending.functionDestinationRgb,
functionDestinationAlpha : renderState.blending.functionDestinationAlpha
},
stencilTest : {
enabled : renderState.stencilTest.enabled,
frontFunction : renderState.stencilTest.frontFunction,
backFunction : renderState.stencilTest.backFunction,
reference : renderState.stencilTest.reference,
mask : renderState.stencilTest.mask,
frontOperation : {
fail : renderState.stencilTest.frontOperation.fail,
zFail : renderState.stencilTest.frontOperation.zFail,
zPass : renderState.stencilTest.frontOperation.zPass
},
backOperation : {
fail : renderState.stencilTest.backOperation.fail,
zFail : renderState.stencilTest.backOperation.zFail,
zPass : renderState.stencilTest.backOperation.zPass
}
},
sampleCoverage : {
enabled : renderState.sampleCoverage.enabled,
value : renderState.sampleCoverage.value,
invert : renderState.sampleCoverage.invert
},
viewport : defined(renderState.viewport) ? BoundingRectangle.clone(renderState.viewport) : undefined
};
};
return RenderState;
});
/*global define*/
define('Renderer/AutomaticUniforms',[
'../Core/Cartesian3',
'../Core/Matrix4',
'../Core/WebGLConstants'
], function(
Cartesian3,
Matrix4,
WebGLConstants) {
'use strict';
/*global WebGLRenderingContext*/
var viewerPositionWCScratch = new Cartesian3();
function AutomaticUniform(options) {
this._size = options.size;
this._datatype = options.datatype;
this.getValue = options.getValue;
}
// this check must use typeof, not defined, because defined doesn't work with undeclared variables.
if (typeof WebGLRenderingContext === 'undefined') {
return {};
}
var datatypeToGlsl = {};
datatypeToGlsl[WebGLConstants.FLOAT] = 'float';
datatypeToGlsl[WebGLConstants.FLOAT_VEC2] = 'vec2';
datatypeToGlsl[WebGLConstants.FLOAT_VEC3] = 'vec3';
datatypeToGlsl[WebGLConstants.FLOAT_VEC4] = 'vec4';
datatypeToGlsl[WebGLConstants.INT] = 'int';
datatypeToGlsl[WebGLConstants.INT_VEC2] = 'ivec2';
datatypeToGlsl[WebGLConstants.INT_VEC3] = 'ivec3';
datatypeToGlsl[WebGLConstants.INT_VEC4] = 'ivec4';
datatypeToGlsl[WebGLConstants.BOOL] = 'bool';
datatypeToGlsl[WebGLConstants.BOOL_VEC2] = 'bvec2';
datatypeToGlsl[WebGLConstants.BOOL_VEC3] = 'bvec3';
datatypeToGlsl[WebGLConstants.BOOL_VEC4] = 'bvec4';
datatypeToGlsl[WebGLConstants.FLOAT_MAT2] = 'mat2';
datatypeToGlsl[WebGLConstants.FLOAT_MAT3] = 'mat3';
datatypeToGlsl[WebGLConstants.FLOAT_MAT4] = 'mat4';
datatypeToGlsl[WebGLConstants.SAMPLER_2D] = 'sampler2D';
datatypeToGlsl[WebGLConstants.SAMPLER_CUBE] = 'samplerCube';
AutomaticUniform.prototype.getDeclaration = function(name) {
var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name;
var size = this._size;
if (size === 1) {
declaration += ';';
} else {
declaration += '[' + size.toString() + '];';
}
return declaration;
};
/**
* @private
*/
var AutomaticUniforms = {
/**
* An automatic GLSL uniform containing the viewport's x
, y
, width
,
* and height
properties in an vec4
's x
, y
, z
,
* and w
components, respectively.
*
* @alias czm_viewport
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec4 czm_viewport;
*
* // Scale the window coordinate components to [0, 1] by dividing
* // by the viewport's width and height.
* vec2 v = gl_FragCoord.xy / czm_viewport.zw;
*
* @see Context#getViewport
*/
czm_viewport : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC4,
getValue : function(uniformState) {
return uniformState.viewportCartesian4;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 orthographic projection matrix that
* transforms window coordinates to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* This transform is useful when a vertex shader inputs or manipulates window coordinates
* as done by {@link BillboardCollection}.
*
* Do not confuse {@link czm_viewportTransformation} with czm_viewportOrthographic
.
* The former transforms from normalized device coordinates to window coordinates; the later transforms
* from window coordinates to clip coordinates, and is often used to assign to gl_Position
.
*
* @alias czm_viewportOrthographic
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_viewportOrthographic;
*
* // Example
* gl_Position = czm_viewportOrthographic * vec4(windowPosition, 0.0, 1.0);
*
* @see UniformState#viewportOrthographic
* @see czm_viewport
* @see czm_viewportTransformation
* @see BillboardCollection
*/
czm_viewportOrthographic : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.viewportOrthographic;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 transformation matrix that
* transforms normalized device coordinates to window coordinates. The context's
* full viewport is used, and the depth range is assumed to be near = 0
* and far = 1
.
*
* This transform is useful when there is a need to manipulate window coordinates
* in a vertex shader as done by {@link BillboardCollection}. In many cases,
* this matrix will not be used directly; instead, {@link czm_modelToWindowCoordinates}
* will be used to transform directly from model to window coordinates.
*
* Do not confuse czm_viewportTransformation
with {@link czm_viewportOrthographic}.
* The former transforms from normalized device coordinates to window coordinates; the later transforms
* from window coordinates to clip coordinates, and is often used to assign to gl_Position
.
*
* @alias czm_viewportTransformation
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_viewportTransformation;
*
* // Use czm_viewportTransformation as part of the
* // transform from model to window coordinates.
* vec4 q = czm_modelViewProjection * positionMC; // model to clip coordinates
* q.xyz /= q.w; // clip to normalized device coordinates (ndc)
* q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // ndc to window coordinates
*
* @see UniformState#viewportTransformation
* @see czm_viewport
* @see czm_viewportOrthographic
* @see czm_modelToWindowCoordinates
* @see BillboardCollection
*/
czm_viewportTransformation : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.viewportTransformation;
}
}),
/**
* An automatic GLSL uniform representing the depth after
* only the globe has been rendered and packed into an RGBA texture.
*
* @private
*
* @alias czm_globeDepthTexture
* @glslUniform
*
* @example
* // GLSL declaration
* uniform sampler2D czm_globeDepthTexture;
*
* // Get the depth at the current fragment
* vec2 coords = gl_FragCoord.xy / czm_viewport.zw;
* float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));
*/
czm_globeDepthTexture : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.SAMPLER_2D,
getValue : function(uniformState) {
return uniformState.globeDepthTexture;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model transformation matrix that
* transforms model coordinates to world coordinates.
*
* @alias czm_model
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_model;
*
* // Example
* vec4 worldPosition = czm_model * modelPosition;
*
* @see UniformState#model
* @see czm_inverseModel
* @see czm_modelView
* @see czm_modelViewProjection
*/
czm_model : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.model;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model transformation matrix that
* transforms world coordinates to model coordinates.
*
* @alias czm_inverseModel
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseModel;
*
* // Example
* vec4 modelPosition = czm_inverseModel * worldPosition;
*
* @see UniformState#inverseModel
* @see czm_model
* @see czm_inverseModelView
*/
czm_inverseModel : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseModel;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 view transformation matrix that
* transforms world coordinates to eye coordinates.
*
* @alias czm_view
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_view;
*
* // Example
* vec4 eyePosition = czm_view * worldPosition;
*
* @see UniformState#view
* @see czm_viewRotation
* @see czm_modelView
* @see czm_viewProjection
* @see czm_modelViewProjection
* @see czm_inverseView
*/
czm_view : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.view;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 view transformation matrix that
* transforms 3D world coordinates to eye coordinates. In 3D mode, this is identical to
* {@link czm_view}, but in 2D and Columbus View it represents the view matrix
* as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* @alias czm_view3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_view3D;
*
* // Example
* vec4 eyePosition3D = czm_view3D * worldPosition3D;
*
* @see UniformState#view3D
* @see czm_view
*/
czm_view3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.view3D;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 view rotation matrix that
* transforms vectors in world coordinates to eye coordinates.
*
* @alias czm_viewRotation
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_viewRotation;
*
* // Example
* vec3 eyeVector = czm_viewRotation * worldVector;
*
* @see UniformState#viewRotation
* @see czm_view
* @see czm_inverseView
* @see czm_inverseViewRotation
*/
czm_viewRotation : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.viewRotation;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 view rotation matrix that
* transforms vectors in 3D world coordinates to eye coordinates. In 3D mode, this is identical to
* {@link czm_viewRotation}, but in 2D and Columbus View it represents the view matrix
* as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* @alias czm_viewRotation3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_viewRotation3D;
*
* // Example
* vec3 eyeVector = czm_viewRotation3D * worldVector;
*
* @see UniformState#viewRotation3D
* @see czm_viewRotation
*/
czm_viewRotation3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.viewRotation3D;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 transformation matrix that
* transforms from eye coordinates to world coordinates.
*
* @alias czm_inverseView
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseView;
*
* // Example
* vec4 worldPosition = czm_inverseView * eyePosition;
*
* @see UniformState#inverseView
* @see czm_view
* @see czm_inverseNormal
*/
czm_inverseView : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseView;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 transformation matrix that
* transforms from 3D eye coordinates to world coordinates. In 3D mode, this is identical to
* {@link czm_inverseView}, but in 2D and Columbus View it represents the inverse view matrix
* as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* @alias czm_inverseView3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseView3D;
*
* // Example
* vec4 worldPosition = czm_inverseView3D * eyePosition;
*
* @see UniformState#inverseView3D
* @see czm_inverseView
*/
czm_inverseView3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseView3D;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 rotation matrix that
* transforms vectors from eye coordinates to world coordinates.
*
* @alias czm_inverseViewRotation
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_inverseViewRotation;
*
* // Example
* vec4 worldVector = czm_inverseViewRotation * eyeVector;
*
* @see UniformState#inverseView
* @see czm_view
* @see czm_viewRotation
* @see czm_inverseViewRotation
*/
czm_inverseViewRotation : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.inverseViewRotation;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 rotation matrix that
* transforms vectors from 3D eye coordinates to world coordinates. In 3D mode, this is identical to
* {@link czm_inverseViewRotation}, but in 2D and Columbus View it represents the inverse view matrix
* as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* @alias czm_inverseViewRotation3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_inverseViewRotation3D;
*
* // Example
* vec4 worldVector = czm_inverseViewRotation3D * eyeVector;
*
* @see UniformState#inverseView3D
* @see czm_inverseViewRotation
*/
czm_inverseViewRotation3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.inverseViewRotation3D;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 projection transformation matrix that
* transforms eye coordinates to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* @alias czm_projection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_projection;
*
* // Example
* gl_Position = czm_projection * eyePosition;
*
* @see UniformState#projection
* @see czm_viewProjection
* @see czm_modelViewProjection
* @see czm_infiniteProjection
*/
czm_projection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.projection;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 inverse projection transformation matrix that
* transforms from clip coordinates to eye coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* @alias czm_inverseProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseProjection;
*
* // Example
* vec4 eyePosition = czm_inverseProjection * clipPosition;
*
* @see UniformState#inverseProjection
* @see czm_projection
*/
czm_inverseProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseProjection;
}
}),
/**
* @private
*/
czm_inverseProjectionOIT : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseProjectionOIT;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity,
* that transforms eye coordinates to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output. An infinite far plane is used
* in algorithms like shadow volumes and GPU ray casting with proxy geometry to ensure that triangles
* are not clipped by the far plane.
*
* @alias czm_infiniteProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_infiniteProjection;
*
* // Example
* gl_Position = czm_infiniteProjection * eyePosition;
*
* @see UniformState#infiniteProjection
* @see czm_projection
* @see czm_modelViewInfiniteProjection
*/
czm_infiniteProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.infiniteProjection;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model-view transformation matrix that
* transforms model coordinates to eye coordinates.
*
* Positions should be transformed to eye coordinates using czm_modelView
and
* normals should be transformed using {@link czm_normal}.
*
* @alias czm_modelView
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_modelView;
*
* // Example
* vec4 eyePosition = czm_modelView * modelPosition;
*
* // The above is equivalent to, but more efficient than:
* vec4 eyePosition = czm_view * czm_model * modelPosition;
*
* @see UniformState#modelView
* @see czm_model
* @see czm_view
* @see czm_modelViewProjection
* @see czm_normal
*/
czm_modelView : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.modelView;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model-view transformation matrix that
* transforms 3D model coordinates to eye coordinates. In 3D mode, this is identical to
* {@link czm_modelView}, but in 2D and Columbus View it represents the model-view matrix
* as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* Positions should be transformed to eye coordinates using czm_modelView3D
and
* normals should be transformed using {@link czm_normal3D}.
*
* @alias czm_modelView3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_modelView3D;
*
* // Example
* vec4 eyePosition = czm_modelView3D * modelPosition;
*
* // The above is equivalent to, but more efficient than:
* vec4 eyePosition = czm_view3D * czm_model * modelPosition;
*
* @see UniformState#modelView3D
* @see czm_modelView
*/
czm_modelView3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.modelView3D;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model-view transformation matrix that
* transforms model coordinates, relative to the eye, to eye coordinates. This is used
* in conjunction with {@link czm_translateRelativeToEye}.
*
* @alias czm_modelViewRelativeToEye
* @glslUniform
*
* @example
* // GLSL declaration
* uniform mat4 czm_modelViewRelativeToEye;
*
* // Example
* attribute vec3 positionHigh;
* attribute vec3 positionLow;
*
* void main()
* {
* vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);
* gl_Position = czm_projection * (czm_modelViewRelativeToEye * p);
* }
*
* @see czm_modelViewProjectionRelativeToEye
* @see czm_translateRelativeToEye
* @see EncodedCartesian3
*/
czm_modelViewRelativeToEye : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.modelViewRelativeToEye;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 transformation matrix that
* transforms from eye coordinates to model coordinates.
*
* @alias czm_inverseModelView
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseModelView;
*
* // Example
* vec4 modelPosition = czm_inverseModelView * eyePosition;
*
* @see UniformState#inverseModelView
* @see czm_modelView
*/
czm_inverseModelView : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseModelView;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 transformation matrix that
* transforms from eye coordinates to 3D model coordinates. In 3D mode, this is identical to
* {@link czm_inverseModelView}, but in 2D and Columbus View it represents the inverse model-view matrix
* as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* @alias czm_inverseModelView3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseModelView3D;
*
* // Example
* vec4 modelPosition = czm_inverseModelView3D * eyePosition;
*
* @see UniformState#inverseModelView
* @see czm_inverseModelView
* @see czm_modelView3D
*/
czm_inverseModelView3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseModelView3D;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that
* transforms world coordinates to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* @alias czm_viewProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_viewProjection;
*
* // Example
* vec4 gl_Position = czm_viewProjection * czm_model * modelPosition;
*
* // The above is equivalent to, but more efficient than:
* gl_Position = czm_projection * czm_view * czm_model * modelPosition;
*
* @see UniformState#viewProjection
* @see czm_view
* @see czm_projection
* @see czm_modelViewProjection
* @see czm_inverseViewProjection
*/
czm_viewProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.viewProjection;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that
* transforms clip coordinates to world coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* @alias czm_inverseViewProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseViewProjection;
*
* // Example
* vec4 worldPosition = czm_inverseViewProjection * clipPosition;
*
* @see UniformState#inverseViewProjection
* @see czm_viewProjection
*/
czm_inverseViewProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseViewProjection;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that
* transforms model coordinates to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* @alias czm_modelViewProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_modelViewProjection;
*
* // Example
* vec4 gl_Position = czm_modelViewProjection * modelPosition;
*
* // The above is equivalent to, but more efficient than:
* gl_Position = czm_projection * czm_view * czm_model * modelPosition;
*
* @see UniformState#modelViewProjection
* @see czm_model
* @see czm_view
* @see czm_projection
* @see czm_modelView
* @see czm_viewProjection
* @see czm_modelViewInfiniteProjection
* @see czm_inverseModelViewProjection
*/
czm_modelViewProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.modelViewProjection;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 inverse model-view-projection transformation matrix that
* transforms clip coordinates to model coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output.
*
* @alias czm_inverseModelViewProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_inverseModelViewProjection;
*
* // Example
* vec4 modelPosition = czm_inverseModelViewProjection * clipPosition;
*
* @see UniformState#modelViewProjection
* @see czm_modelViewProjection
*/
czm_inverseModelViewProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.inverseModelViewProjection;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that
* transforms model coordinates, relative to the eye, to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output. This is used in
* conjunction with {@link czm_translateRelativeToEye}.
*
* @alias czm_modelViewProjectionRelativeToEye
* @glslUniform
*
* @example
* // GLSL declaration
* uniform mat4 czm_modelViewProjectionRelativeToEye;
*
* // Example
* attribute vec3 positionHigh;
* attribute vec3 positionLow;
*
* void main()
* {
* vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);
* gl_Position = czm_modelViewProjectionRelativeToEye * p;
* }
*
* @see czm_modelViewRelativeToEye
* @see czm_translateRelativeToEye
* @see EncodedCartesian3
*/
czm_modelViewProjectionRelativeToEye : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.modelViewProjectionRelativeToEye;
}
}),
/**
* An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that
* transforms model coordinates to clip coordinates. Clip coordinates is the
* coordinate system for a vertex shader's gl_Position
output. The projection matrix places
* the far plane at infinity. This is useful in algorithms like shadow volumes and GPU ray casting with
* proxy geometry to ensure that triangles are not clipped by the far plane.
*
* @alias czm_modelViewInfiniteProjection
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat4 czm_modelViewInfiniteProjection;
*
* // Example
* vec4 gl_Position = czm_modelViewInfiniteProjection * modelPosition;
*
* // The above is equivalent to, but more efficient than:
* gl_Position = czm_infiniteProjection * czm_view * czm_model * modelPosition;
*
* @see UniformState#modelViewInfiniteProjection
* @see czm_model
* @see czm_view
* @see czm_infiniteProjection
* @see czm_modelViewProjection
*/
czm_modelViewInfiniteProjection : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT4,
getValue : function(uniformState) {
return uniformState.modelViewInfiniteProjection;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 normal transformation matrix that
* transforms normal vectors in model coordinates to eye coordinates.
*
* Positions should be transformed to eye coordinates using {@link czm_modelView} and
* normals should be transformed using czm_normal
.
*
* @alias czm_normal
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_normal;
*
* // Example
* vec3 eyeNormal = czm_normal * normal;
*
* @see UniformState#normal
* @see czm_inverseNormal
* @see czm_modelView
*/
czm_normal : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.normal;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 normal transformation matrix that
* transforms normal vectors in 3D model coordinates to eye coordinates.
* In 3D mode, this is identical to
* {@link czm_normal}, but in 2D and Columbus View it represents the normal transformation
* matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* Positions should be transformed to eye coordinates using {@link czm_modelView3D} and
* normals should be transformed using czm_normal3D
.
*
* @alias czm_normal3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_normal3D;
*
* // Example
* vec3 eyeNormal = czm_normal3D * normal;
*
* @see UniformState#normal3D
* @see czm_normal
*/
czm_normal3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.normal3D;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 normal transformation matrix that
* transforms normal vectors in eye coordinates to model coordinates. This is
* the opposite of the transform provided by {@link czm_normal}.
*
* @alias czm_inverseNormal
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_inverseNormal;
*
* // Example
* vec3 normalMC = czm_inverseNormal * normalEC;
*
* @see UniformState#inverseNormal
* @see czm_normal
* @see czm_modelView
* @see czm_inverseView
*/
czm_inverseNormal : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.inverseNormal;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 normal transformation matrix that
* transforms normal vectors in eye coordinates to 3D model coordinates. This is
* the opposite of the transform provided by {@link czm_normal}.
* In 3D mode, this is identical to
* {@link czm_inverseNormal}, but in 2D and Columbus View it represents the inverse normal transformation
* matrix as if the camera were at an equivalent location in 3D mode. This is useful for lighting
* 2D and Columbus View in the same way that 3D is lit.
*
* @alias czm_inverseNormal3D
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_inverseNormal3D;
*
* // Example
* vec3 normalMC = czm_inverseNormal3D * normalEC;
*
* @see UniformState#inverseNormal3D
* @see czm_inverseNormal
*/
czm_inverseNormal3D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.inverseNormal3D;
}
}),
/**
* An automatic GLSL uniform containing height (x
) and height squared (y
)
* of the eye (camera) in the 2D scene in meters.
*
* @alias czm_eyeHeight2D
* @glslUniform
*
* @see UniformState#eyeHeight2D
*/
czm_eyeHeight2D : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC2,
getValue : function(uniformState) {
return uniformState.eyeHeight2D;
}
}),
/**
* An automatic GLSL uniform containing the near distance (x
) and the far distance (y
)
* of the frustum defined by the camera. This is the largest possible frustum, not an individual
* frustum used for multi-frustum rendering.
*
* @alias czm_entireFrustum
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec2 czm_entireFrustum;
*
* // Example
* float frustumLength = czm_entireFrustum.y - czm_entireFrustum.x;
*
* @see UniformState#entireFrustum
* @see czm_currentFrustum
*/
czm_entireFrustum : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC2,
getValue : function(uniformState) {
return uniformState.entireFrustum;
}
}),
/**
* An automatic GLSL uniform containing the near distance (x
) and the far distance (y
)
* of the frustum defined by the camera. This is the individual
* frustum used for multi-frustum rendering.
*
* @alias czm_currentFrustum
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec2 czm_currentFrustum;
*
* // Example
* float frustumLength = czm_currentFrustum.y - czm_currentFrustum.x;
*
* @see UniformState#currentFrustum
* @see czm_entireFrustum
*/
czm_currentFrustum : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC2,
getValue : function(uniformState) {
return uniformState.currentFrustum;
}
}),
/**
* The distances to the frustum planes. The top, bottom, left and right distances are
* the x, y, z, and w components, respectively.
*
* @alias czm_frustumPlanes
* @glslUniform
*/
czm_frustumPlanes : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC4,
getValue : function(uniformState) {
return uniformState.frustumPlanes;
}
}),
/**
* An automatic GLSL uniform representing the sun position in world coordinates.
*
* @alias czm_sunPositionWC
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_sunPositionWC;
*
* @see UniformState#sunPositionWC
* @see czm_sunPositionColumbusView
* @see czm_sunDirectionWC
*/
czm_sunPositionWC : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.sunPositionWC;
}
}),
/**
* An automatic GLSL uniform representing the sun position in Columbus view world coordinates.
*
* @alias czm_sunPositionColumbusView
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_sunPositionColumbusView;
*
* @see UniformState#sunPositionColumbusView
* @see czm_sunPositionWC
*/
czm_sunPositionColumbusView : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.sunPositionColumbusView;
}
}),
/**
* An automatic GLSL uniform representing the normalized direction to the sun in eye coordinates.
* This is commonly used for directional lighting computations.
*
* @alias czm_sunDirectionEC
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_sunDirectionEC;
*
* // Example
* float diffuse = max(dot(czm_sunDirectionEC, normalEC), 0.0);
*
* @see UniformState#sunDirectionEC
* @see czm_moonDirectionEC
* @see czm_sunDirectionWC
*/
czm_sunDirectionEC : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.sunDirectionEC;
}
}),
/**
* An automatic GLSL uniform representing the normalized direction to the sun in world coordinates.
* This is commonly used for directional lighting computations.
*
* @alias czm_sunDirectionWC
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_sunDirectionWC;
*
* @see UniformState#sunDirectionWC
* @see czm_sunPositionWC
* @see czm_sunDirectionEC
*/
czm_sunDirectionWC : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.sunDirectionWC;
}
}),
/**
* An automatic GLSL uniform representing the normalized direction to the moon in eye coordinates.
* This is commonly used for directional lighting computations.
*
* @alias czm_moonDirectionEC
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_moonDirectionEC;
*
* // Example
* float diffuse = max(dot(czm_moonDirectionEC, normalEC), 0.0);
*
* @see UniformState#moonDirectionEC
* @see czm_sunDirectionEC
*/
czm_moonDirectionEC : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.moonDirectionEC;
}
}),
/**
* An automatic GLSL uniform representing the high bits of the camera position in model
* coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering
* as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
*
* @alias czm_encodedCameraPositionMCHigh
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_encodedCameraPositionMCHigh;
*
* @see czm_encodedCameraPositionMCLow
* @see czm_modelViewRelativeToEye
* @see czm_modelViewProjectionRelativeToEye
*/
czm_encodedCameraPositionMCHigh : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.encodedCameraPositionMCHigh;
}
}),
/**
* An automatic GLSL uniform representing the low bits of the camera position in model
* coordinates. This is used for GPU RTE to eliminate jittering artifacts when rendering
* as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
*
* @alias czm_encodedCameraPositionMCLow
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform vec3 czm_encodedCameraPositionMCLow;
*
* @see czm_encodedCameraPositionMCHigh
* @see czm_modelViewRelativeToEye
* @see czm_modelViewProjectionRelativeToEye
*/
czm_encodedCameraPositionMCLow : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return uniformState.encodedCameraPositionMCLow;
}
}),
/**
* An automatic GLSL uniform representing the position of the viewer (camera) in world coordinates.
*
* @alias czm_viewerPositionWC
* @glslUniform
*
* @example
* // GLSL declaration
* uniform vec3 czm_viewerPositionWC;
*/
czm_viewerPositionWC : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_VEC3,
getValue : function(uniformState) {
return Matrix4.getTranslation(uniformState.inverseView, viewerPositionWCScratch);
}
}),
/**
* An automatic GLSL uniform representing the frame number. This uniform is automatically incremented
* every frame.
*
* @alias czm_frameNumber
* @glslUniform
*
* @example
* // GLSL declaration
* uniform float czm_frameNumber;
*/
czm_frameNumber : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT,
getValue : function(uniformState) {
return uniformState.frameState.frameNumber;
}
}),
/**
* An automatic GLSL uniform representing the current morph transition time between
* 2D/Columbus View and 3D, with 0.0 being 2D or Columbus View and 1.0 being 3D.
*
* @alias czm_morphTime
* @glslUniform
*
* @example
* // GLSL declaration
* uniform float czm_morphTime;
*
* // Example
* vec4 p = czm_columbusViewMorph(position2D, position3D, czm_morphTime);
*/
czm_morphTime : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT,
getValue : function(uniformState) {
return uniformState.frameState.morphTime;
}
}),
/**
* An automatic GLSL uniform representing the current {@link SceneMode}, expressed
* as a float.
*
* @alias czm_sceneMode
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform float czm_sceneMode;
*
* // Example
* if (czm_sceneMode == czm_sceneMode2D)
* {
* eyeHeightSq = czm_eyeHeight2D.y;
* }
*
* @see czm_sceneMode2D
* @see czm_sceneModeColumbusView
* @see czm_sceneMode3D
* @see czm_sceneModeMorphing
*/
czm_sceneMode : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT,
getValue : function(uniformState) {
return uniformState.frameState.mode;
}
}),
/**
* An automatic GLSL uniform representing the current rendering pass.
*
* @alias czm_pass
* @glslUniform
*
* @example
* // GLSL declaration
* uniform float czm_pass;
*
* // Example
* if ((czm_pass == czm_passTranslucent) && isOpaque())
* {
* gl_Position *= 0.0; // Cull opaque geometry in the translucent pass
* }
*/
czm_pass : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT,
getValue : function(uniformState) {
return uniformState.pass;
}
}),
/**
* An automatic GLSL uniform representing a 3x3 rotation matrix that transforms
* from True Equator Mean Equinox (TEME) axes to the pseudo-fixed axes at the current scene time.
*
* @alias czm_temeToPseudoFixed
* @glslUniform
*
*
* @example
* // GLSL declaration
* uniform mat3 czm_temeToPseudoFixed;
*
* // Example
* vec3 pseudoFixed = czm_temeToPseudoFixed * teme;
*
* @see UniformState#temeToPseudoFixedMatrix
* @see Transforms.computeTemeToPseudoFixedMatrix
*/
czm_temeToPseudoFixed : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT_MAT3,
getValue : function(uniformState) {
return uniformState.temeToPseudoFixedMatrix;
}
}),
/**
* An automatic GLSL uniform representing the ratio of canvas coordinate space to canvas pixel space.
*
* @alias czm_resolutionScale
* @glslUniform
*
* @example
* uniform float czm_resolutionScale;
*/
czm_resolutionScale : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT,
getValue : function(uniformState) {
return uniformState.resolutionScale;
}
}),
/**
* An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera.
*
* @alias czm_fogDensity
* @glslUniform
*
* @see czm_fog
*/
czm_fogDensity : new AutomaticUniform({
size : 1,
datatype : WebGLConstants.FLOAT,
getValue : function(uniformState) {
return uniformState.fogDensity;
}
})
};
return AutomaticUniforms;
});
/*global define*/
define('Renderer/createUniform',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Color',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Matrix2',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/RuntimeError'
], function(
Cartesian2,
Cartesian3,
Cartesian4,
Color,
defined,
DeveloperError,
Matrix2,
Matrix3,
Matrix4,
RuntimeError) {
'use strict';
/**
* @private
*/
function createUniform(gl, activeUniform, uniformName, location) {
switch (activeUniform.type) {
case gl.FLOAT:
return new UniformFloat(gl, activeUniform, uniformName, location);
case gl.FLOAT_VEC2:
return new UniformFloatVec2(gl, activeUniform, uniformName, location);
case gl.FLOAT_VEC3:
return new UniformFloatVec3(gl, activeUniform, uniformName, location);
case gl.FLOAT_VEC4:
return new UniformFloatVec4(gl, activeUniform, uniformName, location);
case gl.SAMPLER_2D:
case gl.SAMPLER_CUBE:
return new UniformSampler(gl, activeUniform, uniformName, location);
case gl.INT:
case gl.BOOL:
return new UniformInt(gl, activeUniform, uniformName, location);
case gl.INT_VEC2:
case gl.BOOL_VEC2:
return new UniformIntVec2(gl, activeUniform, uniformName, location);
case gl.INT_VEC3:
case gl.BOOL_VEC3:
return new UniformIntVec3(gl, activeUniform, uniformName, location);
case gl.INT_VEC4:
case gl.BOOL_VEC4:
return new UniformIntVec4(gl, activeUniform, uniformName, location);
case gl.FLOAT_MAT2:
return new UniformMat2(gl, activeUniform, uniformName, location);
case gl.FLOAT_MAT3:
return new UniformMat3(gl, activeUniform, uniformName, location);
case gl.FLOAT_MAT4:
return new UniformMat4(gl, activeUniform, uniformName, location);
default:
throw new RuntimeError('Unrecognized uniform type: ' + activeUniform.type + ' for uniform "' + uniformName + '".');
}
}
function UniformFloat(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = 0.0;
this._gl = gl;
this._location = location;
}
UniformFloat.prototype.set = function() {
if (this.value !== this._value) {
this._value = this.value;
this._gl.uniform1f(this._location, this.value);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformFloatVec2(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian2();
this._gl = gl;
this._location = location;
}
UniformFloatVec2.prototype.set = function() {
var v = this.value;
if (!Cartesian2.equals(v, this._value)) {
Cartesian2.clone(v, this._value);
this._gl.uniform2f(this._location, v.x, v.y);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformFloatVec3(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = undefined;
this._gl = gl;
this._location = location;
}
UniformFloatVec3.prototype.set = function() {
var v = this.value;
if (defined(v.red)) {
if (!Color.equals(v, this._value)) {
this._value = Color.clone(v, this._value);
this._gl.uniform3f(this._location, v.red, v.green, v.blue);
}
} else if (defined(v.x)) {
if (!Cartesian3.equals(v, this._value)) {
this._value = Cartesian3.clone(v, this._value);
this._gl.uniform3f(this._location, v.x, v.y, v.z);
}
} else {
throw new DeveloperError('Invalid vec3 value for uniform "' + this._activethis.name + '".');
}
};
///////////////////////////////////////////////////////////////////////////
function UniformFloatVec4(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = undefined;
this._gl = gl;
this._location = location;
}
UniformFloatVec4.prototype.set = function() {
var v = this.value;
if (defined(v.red)) {
if (!Color.equals(v, this._value)) {
this._value = Color.clone(v, this._value);
this._gl.uniform4f(this._location, v.red, v.green, v.blue, v.alpha);
}
} else if (defined(v.x)) {
if (!Cartesian4.equals(v, this._value)) {
this._value = Cartesian4.clone(v, this._value);
this._gl.uniform4f(this._location, v.x, v.y, v.z, v.w);
}
} else {
throw new DeveloperError('Invalid vec4 value for uniform "' + this._activethis.name + '".');
}
};
///////////////////////////////////////////////////////////////////////////
function UniformSampler(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._gl = gl;
this._location = location;
this.textureUnitIndex = undefined;
}
UniformSampler.prototype.set = function() {
var gl = this._gl;
gl.activeTexture(gl.TEXTURE0 + this.textureUnitIndex);
var v = this.value;
gl.bindTexture(v._target, v._texture);
};
UniformSampler.prototype._setSampler = function(textureUnitIndex) {
this.textureUnitIndex = textureUnitIndex;
this._gl.uniform1i(this._location, textureUnitIndex);
return textureUnitIndex + 1;
};
///////////////////////////////////////////////////////////////////////////
function UniformInt(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = 0.0;
this._gl = gl;
this._location = location;
}
UniformInt.prototype.set = function() {
if (this.value !== this._value) {
this._value = this.value;
this._gl.uniform1i(this._location, this.value);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformIntVec2(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian2();
this._gl = gl;
this._location = location;
}
UniformIntVec2.prototype.set = function() {
var v = this.value;
if (!Cartesian2.equals(v, this._value)) {
Cartesian2.clone(v, this._value);
this._gl.uniform2i(this._location, v.x, v.y);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformIntVec3(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian3();
this._gl = gl;
this._location = location;
}
UniformIntVec3.prototype.set = function() {
var v = this.value;
if (!Cartesian3.equals(v, this._value)) {
Cartesian3.clone(v, this._value);
this._gl.uniform3i(this._location, v.x, v.y, v.z);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformIntVec4(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Cartesian4();
this._gl = gl;
this._location = location;
}
UniformIntVec4.prototype.set = function() {
var v = this.value;
if (!Cartesian4.equals(v, this._value)) {
Cartesian4.clone(v, this._value);
this._gl.uniform4i(this._location, v.x, v.y, v.z, v.w);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformMat2(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Float32Array(4);
this._gl = gl;
this._location = location;
}
UniformMat2.prototype.set = function() {
if (!Matrix2.equalsArray(this.value, this._value, 0)) {
Matrix2.toArray(this.value, this._value);
this._gl.uniformMatrix2fv(this._location, false, this._value);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformMat3(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Float32Array(9);
this._gl = gl;
this._location = location;
}
UniformMat3.prototype.set = function() {
if (!Matrix3.equalsArray(this.value, this._value, 0)) {
Matrix3.toArray(this.value, this._value);
this._gl.uniformMatrix3fv(this._location, false, this._value);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformMat4(gl, activeUniform, uniformName, location) {
/**
* @readonly
*/
this.name = uniformName;
this.value = undefined;
this._value = new Float32Array(16);
this._gl = gl;
this._location = location;
}
UniformMat4.prototype.set = function() {
if (!Matrix4.equalsArray(this.value, this._value, 0)) {
Matrix4.toArray(this.value, this._value);
this._gl.uniformMatrix4fv(this._location, false, this._value);
}
};
return createUniform;
});
/*global define*/
define('Renderer/createUniformArray',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Color',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Matrix2',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/RuntimeError'
], function(
Cartesian2,
Cartesian3,
Cartesian4,
Color,
defined,
DeveloperError,
Matrix2,
Matrix3,
Matrix4,
RuntimeError) {
'use strict';
/**
* @private
*/
function createUniformArray(gl, activeUniform, uniformName, locations) {
switch (activeUniform.type) {
case gl.FLOAT:
return new UniformArrayFloat(gl, activeUniform, uniformName, locations);
case gl.FLOAT_VEC2:
return new UniformArrayFloatVec2(gl, activeUniform, uniformName, locations);
case gl.FLOAT_VEC3:
return new UniformArrayFloatVec3(gl, activeUniform, uniformName, locations);
case gl.FLOAT_VEC4:
return new UniformArrayFloatVec4(gl, activeUniform, uniformName, locations);
case gl.SAMPLER_2D:
case gl.SAMPLER_CUBE:
return new UniformArraySampler(gl, activeUniform, uniformName, locations);
case gl.INT:
case gl.BOOL:
return new UniformArrayInt(gl, activeUniform, uniformName, locations);
case gl.INT_VEC2:
case gl.BOOL_VEC2:
return new UniformArrayIntVec2(gl, activeUniform, uniformName, locations);
case gl.INT_VEC3:
case gl.BOOL_VEC3:
return new UniformArrayIntVec3(gl, activeUniform, uniformName, locations);
case gl.INT_VEC4:
case gl.BOOL_VEC4:
return new UniformArrayIntVec4(gl, activeUniform, uniformName, locations);
case gl.FLOAT_MAT2:
return new UniformArrayMat2(gl, activeUniform, uniformName, locations);
case gl.FLOAT_MAT3:
return new UniformArrayMat3(gl, activeUniform, uniformName, locations);
case gl.FLOAT_MAT4:
return new UniformArrayMat4(gl, activeUniform, uniformName, locations);
default:
throw new RuntimeError('Unrecognized uniform type: ' + activeUniform.type + ' for uniform "' + uniformName + '".');
}
}
function UniformArrayFloat(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length);
this._gl = gl;
this._location = locations[0];
}
UniformArrayFloat.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (v !== arraybuffer[i]) {
arraybuffer[i] = v;
changed = true;
}
}
if (changed) {
this._gl.uniform1fv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayFloatVec2(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 2);
this._gl = gl;
this._location = locations[0];
}
UniformArrayFloatVec2.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Cartesian2.equalsArray(v, arraybuffer, j)) {
Cartesian2.pack(v, arraybuffer, j);
changed = true;
}
j += 2;
}
if (changed) {
this._gl.uniform2fv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayFloatVec3(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 3);
this._gl = gl;
this._location = locations[0];
}
UniformArrayFloatVec3.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (defined(v.red)) {
if ((v.red !== arraybuffer[j]) ||
(v.green !== arraybuffer[j + 1]) ||
(v.blue !== arraybuffer[j + 2])) {
arraybuffer[j] = v.red;
arraybuffer[j + 1] = v.green;
arraybuffer[j + 2] = v.blue;
changed = true;
}
} else if (defined(v.x)) {
if (!Cartesian3.equalsArray(v, arraybuffer, j)) {
Cartesian3.pack(v, arraybuffer, j);
changed = true;
}
} else {
throw new DeveloperError('Invalid vec3 value.');
}
j += 3;
}
if (changed) {
this._gl.uniform3fv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayFloatVec4(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 4);
this._gl = gl;
this._location = locations[0];
}
UniformArrayFloatVec4.prototype.set = function() {
// PERFORMANCE_IDEA: if it is a common case that only a few elements
// in a uniform array change, we could use heuristics to determine
// when it is better to call uniform4f for each element that changed
// vs. call uniform4fv once to set the entire array. This applies
// to all uniform array types, not just vec4. We might not care
// once we have uniform buffers since that will be the fast path.
// PERFORMANCE_IDEA: Micro-optimization (I bet it works though):
// As soon as changed is true, break into a separate loop that
// does the copy without the equals check.
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (defined(v.red)) {
if (!Color.equalsArray(v, arraybuffer, j)) {
Color.pack(v, arraybuffer, j);
changed = true;
}
} else if (defined(v.x)) {
if (!Cartesian4.equalsArray(v, arraybuffer, j)) {
Cartesian4.pack(v, arraybuffer, j);
changed = true;
}
} else {
throw new DeveloperError('Invalid vec4 value.');
}
j += 4;
}
if (changed) {
this._gl.uniform4fv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArraySampler(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length);
this._gl = gl;
this._locations = locations;
this.textureUnitIndex = undefined;
}
UniformArraySampler.prototype.set = function() {
var gl = this._gl;
var textureUnitIndex = gl.TEXTURE0 + this.textureUnitIndex;
var value = this.value;
var length = value.length;
for (var i = 0; i < length; ++i) {
var v = value[i];
gl.activeTexture(textureUnitIndex + i);
gl.bindTexture(v._target, v._texture);
}
};
UniformArraySampler.prototype._setSampler = function(textureUnitIndex) {
this.textureUnitIndex = textureUnitIndex;
var locations = this._locations;
var length = locations.length;
for (var i = 0; i < length; ++i) {
var index = textureUnitIndex + i;
this._gl.uniform1i(locations[i], index);
}
return textureUnitIndex + length;
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayInt(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length);
this._gl = gl;
this._location = locations[0];
}
UniformArrayInt.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (v !== arraybuffer[i]) {
arraybuffer[i] = v;
changed = true;
}
}
if (changed) {
this._gl.uniform1iv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayIntVec2(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length * 2);
this._gl = gl;
this._location = locations[0];
}
UniformArrayIntVec2.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Cartesian2.equalsArray(v, arraybuffer, j)) {
Cartesian2.pack(v, arraybuffer, j);
changed = true;
}
j += 2;
}
if (changed) {
this._gl.uniform2iv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayIntVec3(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length * 3);
this._gl = gl;
this._location = locations[0];
}
UniformArrayIntVec3.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Cartesian3.equalsArray(v, arraybuffer, j)) {
Cartesian3.pack(v, arraybuffer, j);
changed = true;
}
j += 3;
}
if (changed) {
this._gl.uniform3iv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayIntVec4(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Int32Array(length * 4);
this._gl = gl;
this._location = locations[0];
}
UniformArrayIntVec4.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Cartesian4.equalsArray(v, arraybuffer, j)) {
Cartesian4.pack(v, arraybuffer, j);
changed = true;
}
j += 4;
}
if (changed) {
this._gl.uniform4iv(this._location, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayMat2(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 4);
this._gl = gl;
this._location = locations[0];
}
UniformArrayMat2.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Matrix2.equalsArray(v, arraybuffer, j)) {
Matrix2.pack(v, arraybuffer, j);
changed = true;
}
j += 4;
}
if (changed) {
this._gl.uniformMatrix2fv(this._location, false, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayMat3(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 9);
this._gl = gl;
this._location = locations[0];
}
UniformArrayMat3.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Matrix3.equalsArray(v, arraybuffer, j)) {
Matrix3.pack(v, arraybuffer, j);
changed = true;
}
j += 9;
}
if (changed) {
this._gl.uniformMatrix3fv(this._location, false, arraybuffer);
}
};
///////////////////////////////////////////////////////////////////////////
function UniformArrayMat4(gl, activeUniform, uniformName, locations) {
var length = locations.length;
/**
* @readonly
*/
this.name = uniformName;
this.value = new Array(length);
this._value = new Float32Array(length * 16);
this._gl = gl;
this._location = locations[0];
}
UniformArrayMat4.prototype.set = function() {
var value = this.value;
var length = value.length;
var arraybuffer = this._value;
var changed = false;
var j = 0;
for (var i = 0; i < length; ++i) {
var v = value[i];
if (!Matrix4.equalsArray(v, arraybuffer, j)) {
Matrix4.pack(v, arraybuffer, j);
changed = true;
}
j += 16;
}
if (changed) {
this._gl.uniformMatrix4fv(this._location, false, arraybuffer);
}
};
return createUniformArray;
});
/*global define*/
define('Renderer/ShaderProgram',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/RuntimeError',
'./AutomaticUniforms',
'./ContextLimits',
'./createUniform',
'./createUniformArray'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
RuntimeError,
AutomaticUniforms,
ContextLimits,
createUniform,
createUniformArray) {
'use strict';
var nextShaderProgramId = 0;
/**
* @private
*/
function ShaderProgram(options) {
var modifiedFS = handleUniformPrecisionMismatches(options.vertexShaderText, options.fragmentShaderText);
this._gl = options.gl;
this._logShaderCompilation = options.logShaderCompilation;
this._debugShaders = options.debugShaders;
this._attributeLocations = options.attributeLocations;
this._program = undefined;
this._numberOfVertexAttributes = undefined;
this._vertexAttributes = undefined;
this._uniformsByName = undefined;
this._uniforms = undefined;
this._automaticUniforms = undefined;
this._manualUniforms = undefined;
this._duplicateUniformNames = modifiedFS.duplicateUniformNames;
this._cachedShader = undefined; // Used by ShaderCache
/**
* @private
*/
this.maximumTextureUnitIndex = undefined;
this._vertexShaderSource = options.vertexShaderSource;
this._vertexShaderText = options.vertexShaderText;
this._fragmentShaderSource = options.fragmentShaderSource;
this._fragmentShaderText = modifiedFS.fragmentShaderText;
/**
* @private
*/
this.id = nextShaderProgramId++;
}
ShaderProgram.fromCache = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
return options.context.shaderCache.getShaderProgram(options);
};
ShaderProgram.replaceCache = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
return options.context.shaderCache.replaceShaderProgram(options);
};
defineProperties(ShaderProgram.prototype, {
/**
* GLSL source for the shader program's vertex shader.
* @memberof ShaderProgram.prototype
*
* @type {ShaderSource}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* GLSL source for the shader program's fragment shader.
* @memberof ShaderProgram.prototype
*
* @type {ShaderSource}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
vertexAttributes : {
get : function() {
initialize(this);
return this._vertexAttributes;
}
},
numberOfVertexAttributes : {
get : function() {
initialize(this);
return this._numberOfVertexAttributes;
}
},
allUniforms : {
get : function() {
initialize(this);
return this._uniformsByName;
}
}
});
function extractUniforms(shaderText) {
var uniformNames = [];
var uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
if (defined(uniformLines)) {
var len = uniformLines.length;
for (var i = 0; i < len; i++) {
var line = uniformLines[i].trim();
var name = line.slice(line.lastIndexOf(' ') + 1);
uniformNames.push(name);
}
}
return uniformNames;
}
function handleUniformPrecisionMismatches(vertexShaderText, fragmentShaderText) {
// If a uniform exists in both the vertex and fragment shader but with different precision qualifiers,
// give the fragment shader uniform a different name. This fixes shader compilation errors on devices
// that only support mediump in the fragment shader.
var duplicateUniformNames = {};
if (!ContextLimits.highpFloatSupported || !ContextLimits.highpIntSupported) {
var i, j;
var uniformName;
var duplicateName;
var vertexShaderUniforms = extractUniforms(vertexShaderText);
var fragmentShaderUniforms = extractUniforms(fragmentShaderText);
var vertexUniformsCount = vertexShaderUniforms.length;
var fragmentUniformsCount = fragmentShaderUniforms.length;
for (i = 0; i < vertexUniformsCount; i++) {
for (j = 0; j < fragmentUniformsCount; j++) {
if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) {
uniformName = vertexShaderUniforms[i];
duplicateName = 'czm_mediump_' + uniformName;
// Update fragmentShaderText with renamed uniforms
var re = new RegExp(uniformName + '\\b', 'g');
fragmentShaderText = fragmentShaderText.replace(re, duplicateName);
duplicateUniformNames[duplicateName] = uniformName;
}
}
}
}
return {
fragmentShaderText : fragmentShaderText,
duplicateUniformNames : duplicateUniformNames
};
}
var consolePrefix = '[Cesium WebGL] ';
function createAndLinkProgram(gl, shader) {
var vsSource = shader._vertexShaderText;
var fsSource = shader._fragmentShaderText;
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsSource);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsSource);
gl.compileShader(fragmentShader);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
var attributeLocations = shader._attributeLocations;
if (defined(attributeLocations)) {
for ( var attribute in attributeLocations) {
if (attributeLocations.hasOwnProperty(attribute)) {
gl.bindAttribLocation(program, attributeLocations[attribute], attribute);
}
}
}
gl.linkProgram(program);
var log;
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
var debugShaders = shader._debugShaders;
// For performance, only check compile errors if there is a linker error.
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
log = gl.getShaderInfoLog(fragmentShader);
console.error(consolePrefix + 'Fragment shader compile log: ' + log);
if (defined(debugShaders)) {
var fragmentSourceTranslation = debugShaders.getTranslatedShaderSource(fragmentShader);
if (fragmentSourceTranslation !== '') {
console.error(consolePrefix + 'Translated fragment shader source:\n' + fragmentSourceTranslation);
} else {
console.error(consolePrefix + 'Fragment shader translation failed.');
}
}
gl.deleteProgram(program);
throw new RuntimeError('Fragment shader failed to compile. Compile log: ' + log);
}
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
log = gl.getShaderInfoLog(vertexShader);
console.error(consolePrefix + 'Vertex shader compile log: ' + log);
if (defined(debugShaders)) {
var vertexSourceTranslation = debugShaders.getTranslatedShaderSource(vertexShader);
if (vertexSourceTranslation !== '') {
console.error(consolePrefix + 'Translated vertex shader source:\n' + vertexSourceTranslation);
} else {
console.error(consolePrefix + 'Vertex shader translation failed.');
}
}
gl.deleteProgram(program);
throw new RuntimeError('Vertex shader failed to compile. Compile log: ' + log);
}
log = gl.getProgramInfoLog(program);
console.error(consolePrefix + 'Shader program link log: ' + log);
if (defined(debugShaders)) {
console.error(consolePrefix + 'Translated vertex shader source:\n' + debugShaders.getTranslatedShaderSource(vertexShader));
console.error(consolePrefix + 'Translated fragment shader source:\n' + debugShaders.getTranslatedShaderSource(fragmentShader));
}
gl.deleteProgram(program);
throw new RuntimeError('Program failed to link. Link log: ' + log);
}
var logShaderCompilation = shader._logShaderCompilation;
if (logShaderCompilation) {
log = gl.getShaderInfoLog(vertexShader);
if (defined(log) && (log.length > 0)) {
console.log(consolePrefix + 'Vertex shader compile log: ' + log);
}
}
if (logShaderCompilation) {
log = gl.getShaderInfoLog(fragmentShader);
if (defined(log) && (log.length > 0)) {
console.log(consolePrefix + 'Fragment shader compile log: ' + log);
}
}
if (logShaderCompilation) {
log = gl.getProgramInfoLog(program);
if (defined(log) && (log.length > 0)) {
console.log(consolePrefix + 'Shader program link log: ' + log);
}
}
return program;
}
function findVertexAttributes(gl, program, numberOfAttributes) {
var attributes = {};
for (var i = 0; i < numberOfAttributes; ++i) {
var attr = gl.getActiveAttrib(program, i);
var location = gl.getAttribLocation(program, attr.name);
attributes[attr.name] = {
name : attr.name,
type : attr.type,
index : location
};
}
return attributes;
}
function findUniforms(gl, program) {
var uniformsByName = {};
var uniforms = [];
var samplerUniforms = [];
var numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (var i = 0; i < numberOfUniforms; ++i) {
var activeUniform = gl.getActiveUniform(program, i);
var suffix = '[0]';
var uniformName = activeUniform.name.indexOf(suffix, activeUniform.name.length - suffix.length) !== -1 ? activeUniform.name.slice(0, activeUniform.name.length - 3) : activeUniform.name;
// Ignore GLSL built-in uniforms returned in Firefox.
if (uniformName.indexOf('gl_') !== 0) {
if (activeUniform.name.indexOf('[') < 0) {
// Single uniform
var location = gl.getUniformLocation(program, uniformName);
// IE 11.0.9 needs this check since getUniformLocation can return null
// if the uniform is not active (e.g., it is optimized out). Looks like
// getActiveUniform() above returns uniforms that are not actually active.
if (location !== null) {
var uniform = createUniform(gl, activeUniform, uniformName, location);
uniformsByName[uniformName] = uniform;
uniforms.push(uniform);
if (uniform._setSampler) {
samplerUniforms.push(uniform);
}
}
} else {
// Uniform array
var uniformArray;
var locations;
var value;
var loc;
// On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented
// as separate uniforms, one for each array element. Check for and handle that case.
var indexOfBracket = uniformName.indexOf('[');
if (indexOfBracket >= 0) {
// We're assuming the array elements show up in numerical order - it seems to be true.
uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)];
// Nexus 4 with Android 4.3 needs this check, because it reports a uniform
// with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader.
if (!defined(uniformArray)) {
continue;
}
locations = uniformArray._locations;
// On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox,
// but the size is not 1 like it is in Firefox. So if we push locations here,
// we'll end up adding too many locations.
if (locations.length <= 1) {
value = uniformArray.value;
loc = gl.getUniformLocation(program, uniformName);
// Workaround for IE 11.0.9. See above.
if (loc !== null) {
locations.push(loc);
value.push(gl.getUniform(program, loc));
}
}
} else {
locations = [];
for (var j = 0; j < activeUniform.size; ++j) {
loc = gl.getUniformLocation(program, uniformName + '[' + j + ']');
// Workaround for IE 11.0.9. See above.
if (loc !== null) {
locations.push(loc);
}
}
uniformArray = createUniformArray(gl, activeUniform, uniformName, locations);
uniformsByName[uniformName] = uniformArray;
uniforms.push(uniformArray);
if (uniformArray._setSampler) {
samplerUniforms.push(uniformArray);
}
}
}
}
}
return {
uniformsByName : uniformsByName,
uniforms : uniforms,
samplerUniforms : samplerUniforms
};
}
function partitionUniforms(shader, uniforms) {
var automaticUniforms = [];
var manualUniforms = [];
for (var uniform in uniforms) {
if (uniforms.hasOwnProperty(uniform)) {
var uniformObject = uniforms[uniform];
var uniformName = uniform;
// if it's a duplicate uniform, use its original name so it is updated correctly
var duplicateUniform = shader._duplicateUniformNames[uniformName];
if (defined(duplicateUniform)) {
uniformObject.name = duplicateUniform;
uniformName = duplicateUniform;
}
var automaticUniform = AutomaticUniforms[uniformName];
if (defined(automaticUniform)) {
automaticUniforms.push({
uniform : uniformObject,
automaticUniform : automaticUniform
});
} else {
manualUniforms.push(uniformObject);
}
}
}
return {
automaticUniforms : automaticUniforms,
manualUniforms : manualUniforms
};
}
function setSamplerUniforms(gl, program, samplerUniforms) {
gl.useProgram(program);
var textureUnitIndex = 0;
var length = samplerUniforms.length;
for (var i = 0; i < length; ++i) {
textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex);
}
gl.useProgram(null);
return textureUnitIndex;
}
function initialize(shader) {
if (defined(shader._program)) {
return;
}
var gl = shader._gl;
var program = createAndLinkProgram(gl, shader, shader._debugShaders);
var numberOfVertexAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
var uniforms = findUniforms(gl, program);
var partitionedUniforms = partitionUniforms(shader, uniforms.uniformsByName);
shader._program = program;
shader._numberOfVertexAttributes = numberOfVertexAttributes;
shader._vertexAttributes = findVertexAttributes(gl, program, numberOfVertexAttributes);
shader._uniformsByName = uniforms.uniformsByName;
shader._uniforms = uniforms.uniforms;
shader._automaticUniforms = partitionedUniforms.automaticUniforms;
shader._manualUniforms = partitionedUniforms.manualUniforms;
shader.maximumTextureUnitIndex = setSamplerUniforms(gl, program, uniforms.samplerUniforms);
}
ShaderProgram.prototype._bind = function() {
initialize(this);
this._gl.useProgram(this._program);
};
ShaderProgram.prototype._setUniforms = function(uniformMap, uniformState, validate) {
var len;
var i;
if (defined(uniformMap)) {
var manualUniforms = this._manualUniforms;
len = manualUniforms.length;
for (i = 0; i < len; ++i) {
var mu = manualUniforms[i];
mu.value = uniformMap[mu.name]();
}
}
var automaticUniforms = this._automaticUniforms;
len = automaticUniforms.length;
for (i = 0; i < len; ++i) {
var au = automaticUniforms[i];
au.uniform.value = au.automaticUniform.getValue(uniformState);
}
///////////////////////////////////////////////////////////////////
// It appears that assigning the uniform values above and then setting them here
// (which makes the GL calls) is faster than removing this loop and making
// the GL calls above. I suspect this is because each GL call pollutes the
// L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
var uniforms = this._uniforms;
len = uniforms.length;
for (i = 0; i < len; ++i) {
uniforms[i].set();
}
if (validate) {
var gl = this._gl;
var program = this._program;
gl.validateProgram(program);
if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
throw new DeveloperError('Program validation failed. Program info log: ' + gl.getProgramInfoLog(program));
}
}
};
ShaderProgram.prototype.isDestroyed = function() {
return false;
};
ShaderProgram.prototype.destroy = function() {
this._cachedShader.cache.releaseShaderProgram(this);
return undefined;
};
ShaderProgram.prototype.finalDestroy = function() {
this._gl.deleteProgram(this._program);
return destroyObject(this);
};
return ShaderProgram;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/degreesPerRadian',[],function() {
'use strict';
return "const float czm_degreesPerRadian = 57.29577951308232;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/depthRange',[],function() {
'use strict';
return "const czm_depthRangeStruct czm_depthRange = czm_depthRangeStruct(0.0, 1.0);\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon1',[],function() {
'use strict';
return "const float czm_epsilon1 = 0.1;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon2',[],function() {
'use strict';
return "const float czm_epsilon2 = 0.01;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon3',[],function() {
'use strict';
return "const float czm_epsilon3 = 0.001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon4',[],function() {
'use strict';
return "const float czm_epsilon4 = 0.0001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon5',[],function() {
'use strict';
return "const float czm_epsilon5 = 0.00001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon6',[],function() {
'use strict';
return "const float czm_epsilon6 = 0.000001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon7',[],function() {
'use strict';
return "const float czm_epsilon7 = 0.0000001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/infinity',[],function() {
'use strict';
return "const float czm_infinity = 5906376272000.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/oneOverPi',[],function() {
'use strict';
return "const float czm_oneOverPi = 0.3183098861837907;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/oneOverTwoPi',[],function() {
'use strict';
return "const float czm_oneOverTwoPi = 0.15915494309189535;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passCompute',[],function() {
'use strict';
return "const float czm_passCompute = 1.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passEnvironment',[],function() {
'use strict';
return "const float czm_passEnvironment = 0.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passGlobe',[],function() {
'use strict';
return "const float czm_passGlobe = 2.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passGround',[],function() {
'use strict';
return "const float czm_passGround = 3.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passOpaque',[],function() {
'use strict';
return "const float czm_passOpaque = 4.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passOverlay',[],function() {
'use strict';
return "const float czm_passOverlay = 6.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passTranslucent',[],function() {
'use strict';
return "const float czm_passTranslucent = 5.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/pi',[],function() {
'use strict';
return "const float czm_pi = 3.141592653589793;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverFour',[],function() {
'use strict';
return "const float czm_piOverFour = 0.7853981633974483;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverSix',[],function() {
'use strict';
return "const float czm_piOverSix = 0.5235987755982988;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverThree',[],function() {
'use strict';
return "const float czm_piOverThree = 1.0471975511965976;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverTwo',[],function() {
'use strict';
return "const float czm_piOverTwo = 1.5707963267948966;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/radiansPerDegree',[],function() {
'use strict';
return "const float czm_radiansPerDegree = 0.017453292519943295;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneMode2D',[],function() {
'use strict';
return "const float czm_sceneMode2D = 2.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneMode3D',[],function() {
'use strict';
return "const float czm_sceneMode3D = 3.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneModeColumbusView',[],function() {
'use strict';
return "const float czm_sceneModeColumbusView = 1.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneModeMorphing',[],function() {
'use strict';
return "const float czm_sceneModeMorphing = 0.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/solarRadius',[],function() {
'use strict';
return "const float czm_solarRadius = 695500000.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/threePiOver2',[],function() {
'use strict';
return "const float czm_threePiOver2 = 4.71238898038469;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/twoPi',[],function() {
'use strict';
return "const float czm_twoPi = 6.283185307179586;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/webMercatorMaxLatitude',[],function() {
'use strict';
return "const float czm_webMercatorMaxLatitude = 1.4844222297453324;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/depthRangeStruct',[],function() {
'use strict';
return "struct czm_depthRangeStruct\n\
{\n\
float near;\n\
float far;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/ellipsoid',[],function() {
'use strict';
return "struct czm_ellipsoid\n\
{\n\
vec3 center;\n\
vec3 radii;\n\
vec3 inverseRadii;\n\
vec3 inverseRadiiSquared;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/material',[],function() {
'use strict';
return "struct czm_material\n\
{\n\
vec3 diffuse;\n\
float specular;\n\
float shininess;\n\
vec3 normal;\n\
vec3 emission;\n\
float alpha;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/materialInput',[],function() {
'use strict';
return "struct czm_materialInput\n\
{\n\
float s;\n\
vec2 st;\n\
vec3 str;\n\
vec3 normalEC;\n\
mat3 tangentToEyeMatrix;\n\
vec3 positionToEyeEC;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/ray',[],function() {
'use strict';
return "struct czm_ray\n\
{\n\
vec3 origin;\n\
vec3 direction;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/raySegment',[],function() {
'use strict';
return "struct czm_raySegment\n\
{\n\
float start;\n\
float stop;\n\
};\n\
const czm_raySegment czm_emptyRaySegment = czm_raySegment(-czm_infinity, -czm_infinity);\n\
const czm_raySegment czm_fullRaySegment = czm_raySegment(0.0, czm_infinity);\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/shadowParameters',[],function() {
'use strict';
return "struct czm_shadowParameters\n\
{\n\
#ifdef USE_CUBE_MAP_SHADOW\n\
vec3 texCoords;\n\
#else\n\
vec2 texCoords;\n\
#endif\n\
float depthBias;\n\
float depth;\n\
float nDotL;\n\
vec2 texelStepSize;\n\
float normalShadingSmooth;\n\
float darkness;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/alphaWeight',[],function() {
'use strict';
return "float czm_alphaWeight(float a)\n\
{\n\
float z;\n\
if (czm_sceneMode != czm_sceneMode2D)\n\
{\n\
float x = 2.0 * (gl_FragCoord.x - czm_viewport.x) / czm_viewport.z - 1.0;\n\
float y = 2.0 * (gl_FragCoord.y - czm_viewport.y) / czm_viewport.w - 1.0;\n\
z = (gl_FragCoord.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2];\n\
vec4 q = vec4(x, y, z, 0.0);\n\
q /= gl_FragCoord.w;\n\
z = (czm_inverseProjectionOIT * q).z;\n\
}\n\
else\n\
{\n\
z = gl_FragCoord.z * (czm_currentFrustum.y - czm_currentFrustum.x) + czm_currentFrustum.x;\n\
}\n\
return pow(a + 0.01, 4.0) + max(1e-2, min(3.0 * 1e3, 100.0 / (1e-5 + pow(abs(z) / 10.0, 3.0) + pow(abs(z) / 200.0, 6.0))));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/antialias',[],function() {
'use strict';
return "vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist, float fuzzFactor)\n\
{\n\
float val1 = clamp(dist / fuzzFactor, 0.0, 1.0);\n\
float val2 = clamp((dist - 0.5) / fuzzFactor, 0.0, 1.0);\n\
val1 = val1 * (1.0 - val2);\n\
val1 = val1 * val1 * (3.0 - (2.0 * val1));\n\
val1 = pow(val1, 0.5);\n\
vec4 midColor = (color1 + color2) * 0.5;\n\
return mix(midColor, currentColor, val1);\n\
}\n\
vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist)\n\
{\n\
return czm_antialias(color1, color2, currentColor, dist, 0.1);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeColor',[],function() {
'use strict';
return "vec4 czm_cascadeColor(vec4 weights)\n\
{\n\
return vec4(1.0, 0.0, 0.0, 1.0) * weights.x +\n\
vec4(0.0, 1.0, 0.0, 1.0) * weights.y +\n\
vec4(0.0, 0.0, 1.0, 1.0) * weights.z +\n\
vec4(1.0, 0.0, 1.0, 1.0) * weights.w;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeDistance',[],function() {
'use strict';
return "uniform vec4 shadowMap_cascadeDistances;\n\
float czm_cascadeDistance(vec4 weights)\n\
{\n\
return dot(shadowMap_cascadeDistances, weights);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeMatrix',[],function() {
'use strict';
return "uniform mat4 shadowMap_cascadeMatrices[4];\n\
mat4 czm_cascadeMatrix(vec4 weights)\n\
{\n\
return shadowMap_cascadeMatrices[0] * weights.x +\n\
shadowMap_cascadeMatrices[1] * weights.y +\n\
shadowMap_cascadeMatrices[2] * weights.z +\n\
shadowMap_cascadeMatrices[3] * weights.w;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeWeights',[],function() {
'use strict';
return "uniform vec4 shadowMap_cascadeSplits[2];\n\
vec4 czm_cascadeWeights(float depthEye)\n\
{\n\
vec4 near = step(shadowMap_cascadeSplits[0], vec4(depthEye));\n\
vec4 far = step(depthEye, shadowMap_cascadeSplits[1]);\n\
return near * far;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/columbusViewMorph',[],function() {
'use strict';
return "vec4 czm_columbusViewMorph(vec4 position2D, vec4 position3D, float time)\n\
{\n\
vec3 p = mix(position2D.xyz, position3D.xyz, time);\n\
return vec4(p, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/computePosition',[],function() {
'use strict';
return "vec4 czm_computePosition();\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cosineAndSine',[],function() {
'use strict';
return "vec2 cordic(float angle)\n\
{\n\
vec2 vector = vec2(6.0725293500888267e-1, 0.0);\n\
float sense = (angle < 0.0) ? -1.0 : 1.0;\n\
mat2 rotation = mat2(1.0, sense, -sense, 1.0);\n\
vector = rotation * vector;\n\
angle -= sense * 7.8539816339744828e-1;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
float factor = sense * 5.0e-1;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 4.6364760900080609e-1;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 2.5e-1;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 2.4497866312686414e-1;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.25e-1;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 1.2435499454676144e-1;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 6.25e-2;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 6.2418809995957350e-2;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 3.125e-2;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 3.1239833430268277e-2;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.5625e-2;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 1.5623728620476831e-2;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 7.8125e-3;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 7.8123410601011111e-3;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 3.90625e-3;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 3.9062301319669718e-3;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.953125e-3;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 1.9531225164788188e-3;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 9.765625e-4;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 9.7656218955931946e-4;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 4.8828125e-4;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 4.8828121119489829e-4;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 2.44140625e-4;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 2.4414062014936177e-4;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.220703125e-4;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 1.2207031189367021e-4;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 6.103515625e-5;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 6.1035156174208773e-5;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 3.0517578125e-5;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 3.0517578115526096e-5;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.52587890625e-5;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 1.5258789061315762e-5;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 7.62939453125e-6;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 7.6293945311019700e-6;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 3.814697265625e-6;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 3.8146972656064961e-6;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.9073486328125e-6;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 1.9073486328101870e-6;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 9.5367431640625e-7;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 9.5367431640596084e-7;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 4.76837158203125e-7;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 4.7683715820308884e-7;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 2.384185791015625e-7;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
angle -= sense * 2.3841857910155797e-7;\n\
sense = (angle < 0.0) ? -1.0 : 1.0;\n\
factor = sense * 1.1920928955078125e-7;\n\
rotation[0][1] = factor;\n\
rotation[1][0] = -factor;\n\
vector = rotation * vector;\n\
return vector;\n\
}\n\
vec2 czm_cosineAndSine(float angle)\n\
{\n\
if (angle < -czm_piOverTwo || angle > czm_piOverTwo)\n\
{\n\
if (angle < 0.0)\n\
{\n\
return -cordic(angle + czm_pi);\n\
}\n\
else\n\
{\n\
return -cordic(angle - czm_pi);\n\
}\n\
}\n\
else\n\
{\n\
return cordic(angle);\n\
}\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/decompressTextureCoordinates',[],function() {
'use strict';
return "vec2 czm_decompressTextureCoordinates(float encoded)\n\
{\n\
float temp = encoded / 4096.0;\n\
float xZeroTo4095 = floor(temp);\n\
float stx = xZeroTo4095 / 4095.0;\n\
float sty = (encoded - xZeroTo4095 * 4096.0) / 4095.0;\n\
return vec2(stx, sty);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/eastNorthUpToEyeCoordinates',[],function() {
'use strict';
return "mat3 czm_eastNorthUpToEyeCoordinates(vec3 positionMC, vec3 normalEC)\n\
{\n\
vec3 tangentMC = normalize(vec3(-positionMC.y, positionMC.x, 0.0));\n\
vec3 tangentEC = normalize(czm_normal3D * tangentMC);\n\
vec3 bitangentEC = normalize(cross(normalEC, tangentEC));\n\
return mat3(\n\
tangentEC.x, tangentEC.y, tangentEC.z,\n\
bitangentEC.x, bitangentEC.y, bitangentEC.z,\n\
normalEC.x, normalEC.y, normalEC.z);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/ellipsoidContainsPoint',[],function() {
'use strict';
return "bool czm_ellipsoidContainsPoint(czm_ellipsoid ellipsoid, vec3 point)\n\
{\n\
vec3 scaled = ellipsoid.inverseRadii * (czm_inverseModelView * vec4(point, 1.0)).xyz;\n\
return (dot(scaled, scaled) <= 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/ellipsoidNew',[],function() {
'use strict';
return "czm_ellipsoid czm_ellipsoidNew(vec3 center, vec3 radii)\n\
{\n\
vec3 inverseRadii = vec3(1.0 / radii.x, 1.0 / radii.y, 1.0 / radii.z);\n\
vec3 inverseRadiiSquared = inverseRadii * inverseRadii;\n\
czm_ellipsoid temp = czm_ellipsoid(center, radii, inverseRadii, inverseRadiiSquared);\n\
return temp;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/ellipsoidWgs84TextureCoordinates',[],function() {
'use strict';
return "vec2 czm_ellipsoidWgs84TextureCoordinates(vec3 normal)\n\
{\n\
return vec2(atan(normal.y, normal.x) * czm_oneOverTwoPi + 0.5, asin(normal.z) * czm_oneOverPi + 0.5);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/equalsEpsilon',[],function() {
'use strict';
return "bool czm_equalsEpsilon(vec4 left, vec4 right, float epsilon) {\n\
return all(lessThanEqual(abs(left - right), vec4(epsilon)));\n\
}\n\
bool czm_equalsEpsilon(vec3 left, vec3 right, float epsilon) {\n\
return all(lessThanEqual(abs(left - right), vec3(epsilon)));\n\
}\n\
bool czm_equalsEpsilon(vec2 left, vec2 right, float epsilon) {\n\
return all(lessThanEqual(abs(left - right), vec2(epsilon)));\n\
}\n\
bool czm_equalsEpsilon(float left, float right, float epsilon) {\n\
return (abs(left - right) <= epsilon);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/eyeOffset',[],function() {
'use strict';
return "vec4 czm_eyeOffset(vec4 positionEC, vec3 eyeOffset)\n\
{\n\
vec4 p = positionEC;\n\
vec4 zEyeOffset = normalize(p) * eyeOffset.z;\n\
p.xy += eyeOffset.xy + zEyeOffset.xy;\n\
p.z += zEyeOffset.z;\n\
return p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/eyeToWindowCoordinates',[],function() {
'use strict';
return "vec4 czm_eyeToWindowCoordinates(vec4 positionEC)\n\
{\n\
vec4 q = czm_projection * positionEC;\n\
q.xyz /= q.w;\n\
q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz;\n\
return q;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/fog',[],function() {
'use strict';
return "vec3 czm_fog(float distanceToCamera, vec3 color, vec3 fogColor)\n\
{\n\
float scalar = distanceToCamera * czm_fogDensity;\n\
float fog = 1.0 - exp(-(scalar * scalar));\n\
return mix(color, fogColor, fog);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/geodeticSurfaceNormal',[],function() {
'use strict';
return "vec3 czm_geodeticSurfaceNormal(vec3 positionOnEllipsoid, vec3 ellipsoidCenter, vec3 oneOverEllipsoidRadiiSquared)\n\
{\n\
return normalize((positionOnEllipsoid - ellipsoidCenter) * oneOverEllipsoidRadiiSquared);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getDefaultMaterial',[],function() {
'use strict';
return "czm_material czm_getDefaultMaterial(czm_materialInput materialInput)\n\
{\n\
czm_material material;\n\
material.diffuse = vec3(0.0);\n\
material.specular = 0.0;\n\
material.shininess = 1.0;\n\
material.normal = materialInput.normalEC;\n\
material.emission = vec3(0.0);\n\
material.alpha = 1.0;\n\
return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getLambertDiffuse',[],function() {
'use strict';
return "float czm_getLambertDiffuse(vec3 lightDirectionEC, vec3 normalEC)\n\
{\n\
return max(dot(lightDirectionEC, normalEC), 0.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getSpecular',[],function() {
'use strict';
return "float czm_getSpecular(vec3 lightDirectionEC, vec3 toEyeEC, vec3 normalEC, float shininess)\n\
{\n\
vec3 toReflectedLight = reflect(-lightDirectionEC, normalEC);\n\
float specular = max(dot(toReflectedLight, toEyeEC), 0.0);\n\
return pow(specular, max(shininess, czm_epsilon2));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getWaterNoise',[],function() {
'use strict';
return "vec4 czm_getWaterNoise(sampler2D normalMap, vec2 uv, float time, float angleInRadians)\n\
{\n\
float cosAngle = cos(angleInRadians);\n\
float sinAngle = sin(angleInRadians);\n\
vec2 s0 = vec2(1.0/17.0, 0.0);\n\
vec2 s1 = vec2(-1.0/29.0, 0.0);\n\
vec2 s2 = vec2(1.0/101.0, 1.0/59.0);\n\
vec2 s3 = vec2(-1.0/109.0, -1.0/57.0);\n\
s0 = vec2((cosAngle * s0.x) - (sinAngle * s0.y), (sinAngle * s0.x) + (cosAngle * s0.y));\n\
s1 = vec2((cosAngle * s1.x) - (sinAngle * s1.y), (sinAngle * s1.x) + (cosAngle * s1.y));\n\
s2 = vec2((cosAngle * s2.x) - (sinAngle * s2.y), (sinAngle * s2.x) + (cosAngle * s2.y));\n\
s3 = vec2((cosAngle * s3.x) - (sinAngle * s3.y), (sinAngle * s3.x) + (cosAngle * s3.y));\n\
vec2 uv0 = (uv/103.0) + (time * s0);\n\
vec2 uv1 = uv/107.0 + (time * s1) + vec2(0.23);\n\
vec2 uv2 = uv/vec2(897.0, 983.0) + (time * s2) + vec2(0.51);\n\
vec2 uv3 = uv/vec2(991.0, 877.0) + (time * s3) + vec2(0.71);\n\
uv0 = fract(uv0);\n\
uv1 = fract(uv1);\n\
uv2 = fract(uv2);\n\
uv3 = fract(uv3);\n\
vec4 noise = (texture2D(normalMap, uv0)) +\n\
(texture2D(normalMap, uv1)) +\n\
(texture2D(normalMap, uv2)) +\n\
(texture2D(normalMap, uv3));\n\
return ((noise / 4.0) - 0.5) * 2.0;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getWgs84EllipsoidEC',[],function() {
'use strict';
return "czm_ellipsoid czm_getWgs84EllipsoidEC()\n\
{\n\
vec3 radii = vec3(6378137.0, 6378137.0, 6356752.314245);\n\
vec3 inverseRadii = vec3(1.0 / radii.x, 1.0 / radii.y, 1.0 / radii.z);\n\
vec3 inverseRadiiSquared = inverseRadii * inverseRadii;\n\
czm_ellipsoid temp = czm_ellipsoid(czm_view[3].xyz, radii, inverseRadii, inverseRadiiSquared);\n\
return temp;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/hue',[],function() {
'use strict';
return "vec3 czm_hue(vec3 rgb, float adjustment)\n\
{\n\
const mat3 toYIQ = mat3(0.299, 0.587, 0.114,\n\
0.595716, -0.274453, -0.321263,\n\
0.211456, -0.522591, 0.311135);\n\
const mat3 toRGB = mat3(1.0, 0.9563, 0.6210,\n\
1.0, -0.2721, -0.6474,\n\
1.0, -1.107, 1.7046);\n\
vec3 yiq = toYIQ * rgb;\n\
float hue = atan(yiq.z, yiq.y) + adjustment;\n\
float chroma = sqrt(yiq.z * yiq.z + yiq.y * yiq.y);\n\
vec3 color = vec3(yiq.x, chroma * cos(hue), chroma * sin(hue));\n\
return toRGB * color;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/isEmpty',[],function() {
'use strict';
return "bool czm_isEmpty(czm_raySegment interval)\n\
{\n\
return (interval.stop < 0.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/isFull',[],function() {
'use strict';
return "bool czm_isFull(czm_raySegment interval)\n\
{\n\
return (interval.start == 0.0 && interval.stop == czm_infinity);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/latitudeToWebMercatorFraction',[],function() {
'use strict';
return "float czm_latitudeToWebMercatorFraction(float latitude, float southMercatorY, float oneOverMercatorHeight)\n\
{\n\
float sinLatitude = sin(latitude);\n\
float mercatorY = 0.5 * log((1.0 + sinLatitude) / (1.0 - sinLatitude));\n\
return (mercatorY - southMercatorY) * oneOverMercatorHeight;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/luminance',[],function() {
'use strict';
return "float czm_luminance(vec3 rgb)\n\
{\n\
const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n\
return dot(rgb, W);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/metersPerPixel',[],function() {
'use strict';
return "float czm_metersPerPixel(vec4 positionEC)\n\
{\n\
float width = czm_viewport.z;\n\
float height = czm_viewport.w;\n\
float pixelWidth;\n\
float pixelHeight;\n\
float top = czm_frustumPlanes.x;\n\
float bottom = czm_frustumPlanes.y;\n\
float left = czm_frustumPlanes.z;\n\
float right = czm_frustumPlanes.w;\n\
if (czm_sceneMode == czm_sceneMode2D)\n\
{\n\
float frustumWidth = right - left;\n\
float frustumHeight = top - bottom;\n\
pixelWidth = frustumWidth / width;\n\
pixelHeight = frustumHeight / height;\n\
}\n\
else\n\
{\n\
float distanceToPixel = -positionEC.z;\n\
float inverseNear = 1.0 / czm_currentFrustum.x;\n\
float tanTheta = top * inverseNear;\n\
pixelHeight = 2.0 * distanceToPixel * tanTheta / height;\n\
tanTheta = right * inverseNear;\n\
pixelWidth = 2.0 * distanceToPixel * tanTheta / width;\n\
}\n\
return max(pixelWidth, pixelHeight);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/modelToWindowCoordinates',[],function() {
'use strict';
return "vec4 czm_modelToWindowCoordinates(vec4 position)\n\
{\n\
vec4 q = czm_modelViewProjection * position;\n\
q.xyz /= q.w;\n\
q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz;\n\
return q;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/multiplyWithColorBalance',[],function() {
'use strict';
return "vec3 czm_multiplyWithColorBalance(vec3 left, vec3 right)\n\
{\n\
const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n\
vec3 target = left * right;\n\
float leftLuminance = dot(left, W);\n\
float rightLuminance = dot(right, W);\n\
float targetLuminance = dot(target, W);\n\
return ((leftLuminance + rightLuminance) / (2.0 * targetLuminance)) * target;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/nearFarScalar',[],function() {
'use strict';
return "float czm_nearFarScalar(vec4 nearFarScalar, float cameraDistSq)\n\
{\n\
float valueAtMin = nearFarScalar.y;\n\
float valueAtMax = nearFarScalar.w;\n\
float nearDistanceSq = nearFarScalar.x * nearFarScalar.x;\n\
float farDistanceSq = nearFarScalar.z * nearFarScalar.z;\n\
float t = (cameraDistSq - nearDistanceSq) / (farDistanceSq - nearDistanceSq);\n\
t = pow(clamp(t, 0.0, 1.0), 0.2);\n\
return mix(valueAtMin, valueAtMax, t);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/octDecode',[],function() {
'use strict';
return "vec3 czm_octDecode(vec2 encoded)\n\
{\n\
encoded = encoded / 255.0 * 2.0 - 1.0;\n\
vec3 v = vec3(encoded.x, encoded.y, 1.0 - abs(encoded.x) - abs(encoded.y));\n\
if (v.z < 0.0)\n\
{\n\
v.xy = (1.0 - abs(v.yx)) * czm_signNotZero(v.xy);\n\
}\n\
return normalize(v);\n\
}\n\
vec3 czm_octDecode(float encoded)\n\
{\n\
float temp = encoded / 256.0;\n\
float x = floor(temp);\n\
float y = (temp - x) * 256.0;\n\
return czm_octDecode(vec2(x, y));\n\
}\n\
void czm_octDecode(vec2 encoded, out vec3 vector1, out vec3 vector2, out vec3 vector3)\n\
{\n\
float temp = encoded.x / 65536.0;\n\
float x = floor(temp);\n\
float encodedFloat1 = (temp - x) * 65536.0;\n\
temp = encoded.y / 65536.0;\n\
float y = floor(temp);\n\
float encodedFloat2 = (temp - y) * 65536.0;\n\
vector1 = czm_octDecode(encodedFloat1);\n\
vector2 = czm_octDecode(encodedFloat2);\n\
vector3 = czm_octDecode(vec2(x, y));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/packDepth',[],function() {
'use strict';
return "vec4 czm_packDepth(float depth)\n\
{\n\
vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * depth;\n\
enc = fract(enc);\n\
enc -= enc.yzww * vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0);\n\
return enc;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/phong',[],function() {
'use strict';
return "float czm_private_getLambertDiffuseOfMaterial(vec3 lightDirectionEC, czm_material material)\n\
{\n\
return czm_getLambertDiffuse(lightDirectionEC, material.normal);\n\
}\n\
float czm_private_getSpecularOfMaterial(vec3 lightDirectionEC, vec3 toEyeEC, czm_material material)\n\
{\n\
return czm_getSpecular(lightDirectionEC, toEyeEC, material.normal, material.shininess);\n\
}\n\
vec4 czm_phong(vec3 toEye, czm_material material)\n\
{\n\
float diffuse = czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 0.0, 1.0), material);\n\
if (czm_sceneMode == czm_sceneMode3D) {\n\
diffuse += czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 1.0, 0.0), material);\n\
}\n\
float specular = czm_private_getSpecularOfMaterial(czm_sunDirectionEC, toEye, material) + czm_private_getSpecularOfMaterial(czm_moonDirectionEC, toEye, material);\n\
vec3 materialDiffuse = material.diffuse * 0.5;\n\
vec3 ambient = materialDiffuse;\n\
vec3 color = ambient + material.emission;\n\
color += materialDiffuse * diffuse;\n\
color += material.specular * specular;\n\
return vec4(color, material.alpha);\n\
}\n\
vec4 czm_private_phong(vec3 toEye, czm_material material)\n\
{\n\
float diffuse = czm_private_getLambertDiffuseOfMaterial(czm_sunDirectionEC, material);\n\
float specular = czm_private_getSpecularOfMaterial(czm_sunDirectionEC, toEye, material);\n\
vec3 ambient = vec3(0.0);\n\
vec3 color = ambient + material.emission;\n\
color += material.diffuse * diffuse;\n\
color += material.specular * specular;\n\
return vec4(color, material.alpha);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/pointAlongRay',[],function() {
'use strict';
return "vec3 czm_pointAlongRay(czm_ray ray, float time)\n\
{\n\
return ray.origin + (time * ray.direction);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/rayEllipsoidIntersectionInterval',[],function() {
'use strict';
return "czm_raySegment czm_rayEllipsoidIntersectionInterval(czm_ray ray, czm_ellipsoid ellipsoid)\n\
{\n\
vec3 q = ellipsoid.inverseRadii * (czm_inverseModelView * vec4(ray.origin, 1.0)).xyz;\n\
vec3 w = ellipsoid.inverseRadii * (czm_inverseModelView * vec4(ray.direction, 0.0)).xyz;\n\
q = q - ellipsoid.inverseRadii * (czm_inverseModelView * vec4(ellipsoid.center, 1.0)).xyz;\n\
float q2 = dot(q, q);\n\
float qw = dot(q, w);\n\
if (q2 > 1.0)\n\
{\n\
if (qw >= 0.0)\n\
{\n\
return czm_emptyRaySegment;\n\
}\n\
else\n\
{\n\
float qw2 = qw * qw;\n\
float difference = q2 - 1.0;\n\
float w2 = dot(w, w);\n\
float product = w2 * difference;\n\
if (qw2 < product)\n\
{\n\
return czm_emptyRaySegment;\n\
}\n\
else if (qw2 > product)\n\
{\n\
float discriminant = qw * qw - product;\n\
float temp = -qw + sqrt(discriminant);\n\
float root0 = temp / w2;\n\
float root1 = difference / temp;\n\
if (root0 < root1)\n\
{\n\
czm_raySegment i = czm_raySegment(root0, root1);\n\
return i;\n\
}\n\
else\n\
{\n\
czm_raySegment i = czm_raySegment(root1, root0);\n\
return i;\n\
}\n\
}\n\
else\n\
{\n\
float root = sqrt(difference / w2);\n\
czm_raySegment i = czm_raySegment(root, root);\n\
return i;\n\
}\n\
}\n\
}\n\
else if (q2 < 1.0)\n\
{\n\
float difference = q2 - 1.0;\n\
float w2 = dot(w, w);\n\
float product = w2 * difference;\n\
float discriminant = qw * qw - product;\n\
float temp = -qw + sqrt(discriminant);\n\
czm_raySegment i = czm_raySegment(0.0, temp / w2);\n\
return i;\n\
}\n\
else\n\
{\n\
if (qw < 0.0)\n\
{\n\
float w2 = dot(w, w);\n\
czm_raySegment i = czm_raySegment(0.0, -qw / w2);\n\
return i;\n\
}\n\
else\n\
{\n\
return czm_emptyRaySegment;\n\
}\n\
}\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/RGBToXYZ',[],function() {
'use strict';
return "vec3 czm_RGBToXYZ(vec3 rgb)\n\
{\n\
const mat3 RGB2XYZ = mat3(0.4124, 0.2126, 0.0193,\n\
0.3576, 0.7152, 0.1192,\n\
0.1805, 0.0722, 0.9505);\n\
vec3 xyz = RGB2XYZ * rgb;\n\
vec3 Yxy;\n\
Yxy.r = xyz.g;\n\
float temp = dot(vec3(1.0), xyz);\n\
Yxy.gb = xyz.rg / temp;\n\
return Yxy;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/saturation',[],function() {
'use strict';
return "vec3 czm_saturation(vec3 rgb, float adjustment)\n\
{\n\
const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n\
vec3 intensity = vec3(dot(rgb, W));\n\
return mix(intensity, rgb, adjustment);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/shadowDepthCompare',[],function() {
'use strict';
return "float czm_sampleShadowMap(samplerCube shadowMap, vec3 d)\n\
{\n\
return czm_unpackDepth(textureCube(shadowMap, d));\n\
}\n\
float czm_sampleShadowMap(sampler2D shadowMap, vec2 uv)\n\
{\n\
#ifdef USE_SHADOW_DEPTH_TEXTURE\n\
return texture2D(shadowMap, uv).r;\n\
#else\n\
return czm_unpackDepth(texture2D(shadowMap, uv));\n\
#endif\n\
}\n\
float czm_shadowDepthCompare(samplerCube shadowMap, vec3 uv, float depth)\n\
{\n\
return step(depth, czm_sampleShadowMap(shadowMap, uv));\n\
}\n\
float czm_shadowDepthCompare(sampler2D shadowMap, vec2 uv, float depth)\n\
{\n\
return step(depth, czm_sampleShadowMap(shadowMap, uv));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/shadowVisibility',[],function() {
'use strict';
return "float czm_private_shadowVisibility(float visibility, float nDotL, float normalShadingSmooth, float darkness)\n\
{\n\
#ifdef USE_NORMAL_SHADING\n\
#ifdef USE_NORMAL_SHADING_SMOOTH\n\
float strength = clamp(nDotL / normalShadingSmooth, 0.0, 1.0);\n\
#else\n\
float strength = step(0.0, nDotL);\n\
#endif\n\
visibility *= strength;\n\
#endif\n\
visibility = max(visibility, darkness);\n\
return visibility;\n\
}\n\
#ifdef USE_CUBE_MAP_SHADOW\n\
float czm_shadowVisibility(samplerCube shadowMap, czm_shadowParameters shadowParameters)\n\
{\n\
float depthBias = shadowParameters.depthBias;\n\
float depth = shadowParameters.depth;\n\
float nDotL = shadowParameters.nDotL;\n\
float normalShadingSmooth = shadowParameters.normalShadingSmooth;\n\
float darkness = shadowParameters.darkness;\n\
vec3 uvw = shadowParameters.texCoords;\n\
depth -= depthBias;\n\
float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);\n\
return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);\n\
}\n\
#else\n\
float czm_shadowVisibility(sampler2D shadowMap, czm_shadowParameters shadowParameters)\n\
{\n\
float depthBias = shadowParameters.depthBias;\n\
float depth = shadowParameters.depth;\n\
float nDotL = shadowParameters.nDotL;\n\
float normalShadingSmooth = shadowParameters.normalShadingSmooth;\n\
float darkness = shadowParameters.darkness;\n\
vec2 uv = shadowParameters.texCoords;\n\
depth -= depthBias;\n\
#ifdef USE_SOFT_SHADOWS\n\
vec2 texelStepSize = shadowParameters.texelStepSize;\n\
float radius = 1.0;\n\
float dx0 = -texelStepSize.x * radius;\n\
float dy0 = -texelStepSize.y * radius;\n\
float dx1 = texelStepSize.x * radius;\n\
float dy1 = texelStepSize.y * radius;\n\
float visibility = (\n\
czm_shadowDepthCompare(shadowMap, uv, depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy0), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy0), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy0), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, 0.0), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, 0.0), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy1), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy1), depth) +\n\
czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy1), depth)\n\
) * (1.0 / 9.0);\n\
#else\n\
float visibility = czm_shadowDepthCompare(shadowMap, uv, depth);\n\
#endif\n\
return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);\n\
}\n\
#endif\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/signNotZero',[],function() {
'use strict';
return "float czm_signNotZero(float value)\n\
{\n\
return value >= 0.0 ? 1.0 : -1.0;\n\
}\n\
vec2 czm_signNotZero(vec2 value)\n\
{\n\
return vec2(czm_signNotZero(value.x), czm_signNotZero(value.y));\n\
}\n\
vec3 czm_signNotZero(vec3 value)\n\
{\n\
return vec3(czm_signNotZero(value.x), czm_signNotZero(value.y), czm_signNotZero(value.z));\n\
}\n\
vec4 czm_signNotZero(vec4 value)\n\
{\n\
return vec4(czm_signNotZero(value.x), czm_signNotZero(value.y), czm_signNotZero(value.z), czm_signNotZero(value.w));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/tangentToEyeSpaceMatrix',[],function() {
'use strict';
return "mat3 czm_tangentToEyeSpaceMatrix(vec3 normalEC, vec3 tangentEC, vec3 binormalEC)\n\
{\n\
vec3 normal = normalize(normalEC);\n\
vec3 tangent = normalize(tangentEC);\n\
vec3 binormal = normalize(binormalEC);\n\
return mat3(tangent.x, tangent.y, tangent.z,\n\
binormal.x, binormal.y, binormal.z,\n\
normal.x, normal.y, normal.z);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/translateRelativeToEye',[],function() {
'use strict';
return "vec4 czm_translateRelativeToEye(vec3 high, vec3 low)\n\
{\n\
vec3 highDifference = high - czm_encodedCameraPositionMCHigh;\n\
vec3 lowDifference = low - czm_encodedCameraPositionMCLow;\n\
return vec4(highDifference + lowDifference, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/translucentPhong',[],function() {
'use strict';
return "vec4 czm_translucentPhong(vec3 toEye, czm_material material)\n\
{\n\
float diffuse = czm_getLambertDiffuse(vec3(0.0, 0.0, 1.0), material.normal);\n\
if (czm_sceneMode == czm_sceneMode3D) {\n\
diffuse += czm_getLambertDiffuse(vec3(0.0, 1.0, 0.0), material.normal);\n\
}\n\
diffuse = clamp(diffuse, 0.0, 1.0);\n\
float specular = czm_getSpecular(czm_sunDirectionEC, toEye, material.normal, material.shininess);\n\
specular += czm_getSpecular(czm_moonDirectionEC, toEye, material.normal, material.shininess);\n\
vec3 materialDiffuse = material.diffuse * 0.5;\n\
vec3 ambient = materialDiffuse;\n\
vec3 color = ambient + material.emission;\n\
color += materialDiffuse * diffuse;\n\
color += material.specular * specular;\n\
return vec4(color, material.alpha);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/transpose',[],function() {
'use strict';
return "mat2 czm_transpose(mat2 matrix)\n\
{\n\
return mat2(\n\
matrix[0][0], matrix[1][0],\n\
matrix[0][1], matrix[1][1]);\n\
}\n\
mat3 czm_transpose(mat3 matrix)\n\
{\n\
return mat3(\n\
matrix[0][0], matrix[1][0], matrix[2][0],\n\
matrix[0][1], matrix[1][1], matrix[2][1],\n\
matrix[0][2], matrix[1][2], matrix[2][2]);\n\
}\n\
mat4 czm_transpose(mat4 matrix)\n\
{\n\
return mat4(\n\
matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],\n\
matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],\n\
matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],\n\
matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/unpackDepth',[],function() {
'use strict';
return "float czm_unpackDepth(vec4 packedDepth)\n\
{\n\
return dot(packedDepth, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/windowToEyeCoordinates',[],function() {
'use strict';
return "vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)\n\
{\n\
float x = 2.0 * (fragmentCoordinate.x - czm_viewport.x) / czm_viewport.z - 1.0;\n\
float y = 2.0 * (fragmentCoordinate.y - czm_viewport.y) / czm_viewport.w - 1.0;\n\
float z = (fragmentCoordinate.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2];\n\
vec4 q = vec4(x, y, z, 1.0);\n\
q /= fragmentCoordinate.w;\n\
q = czm_inverseProjection * q;\n\
return q;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/XYZToRGB',[],function() {
'use strict';
return "vec3 czm_XYZToRGB(vec3 Yxy)\n\
{\n\
const mat3 XYZ2RGB = mat3( 3.2405, -0.9693, 0.0556,\n\
-1.5371, 1.8760, -0.2040,\n\
-0.4985, 0.0416, 1.0572);\n\
vec3 xyz;\n\
xyz.r = Yxy.r * Yxy.g / Yxy.b;\n\
xyz.g = Yxy.r;\n\
xyz.b = Yxy.r * (1.0 - Yxy.g - Yxy.b) / Yxy.b;\n\
return XYZ2RGB * xyz;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/CzmBuiltins',[
'./Constants/degreesPerRadian',
'./Constants/depthRange',
'./Constants/epsilon1',
'./Constants/epsilon2',
'./Constants/epsilon3',
'./Constants/epsilon4',
'./Constants/epsilon5',
'./Constants/epsilon6',
'./Constants/epsilon7',
'./Constants/infinity',
'./Constants/oneOverPi',
'./Constants/oneOverTwoPi',
'./Constants/passCompute',
'./Constants/passEnvironment',
'./Constants/passGlobe',
'./Constants/passGround',
'./Constants/passOpaque',
'./Constants/passOverlay',
'./Constants/passTranslucent',
'./Constants/pi',
'./Constants/piOverFour',
'./Constants/piOverSix',
'./Constants/piOverThree',
'./Constants/piOverTwo',
'./Constants/radiansPerDegree',
'./Constants/sceneMode2D',
'./Constants/sceneMode3D',
'./Constants/sceneModeColumbusView',
'./Constants/sceneModeMorphing',
'./Constants/solarRadius',
'./Constants/threePiOver2',
'./Constants/twoPi',
'./Constants/webMercatorMaxLatitude',
'./Structs/depthRangeStruct',
'./Structs/ellipsoid',
'./Structs/material',
'./Structs/materialInput',
'./Structs/ray',
'./Structs/raySegment',
'./Structs/shadowParameters',
'./Functions/alphaWeight',
'./Functions/antialias',
'./Functions/cascadeColor',
'./Functions/cascadeDistance',
'./Functions/cascadeMatrix',
'./Functions/cascadeWeights',
'./Functions/columbusViewMorph',
'./Functions/computePosition',
'./Functions/cosineAndSine',
'./Functions/decompressTextureCoordinates',
'./Functions/eastNorthUpToEyeCoordinates',
'./Functions/ellipsoidContainsPoint',
'./Functions/ellipsoidNew',
'./Functions/ellipsoidWgs84TextureCoordinates',
'./Functions/equalsEpsilon',
'./Functions/eyeOffset',
'./Functions/eyeToWindowCoordinates',
'./Functions/fog',
'./Functions/geodeticSurfaceNormal',
'./Functions/getDefaultMaterial',
'./Functions/getLambertDiffuse',
'./Functions/getSpecular',
'./Functions/getWaterNoise',
'./Functions/getWgs84EllipsoidEC',
'./Functions/hue',
'./Functions/isEmpty',
'./Functions/isFull',
'./Functions/latitudeToWebMercatorFraction',
'./Functions/luminance',
'./Functions/metersPerPixel',
'./Functions/modelToWindowCoordinates',
'./Functions/multiplyWithColorBalance',
'./Functions/nearFarScalar',
'./Functions/octDecode',
'./Functions/packDepth',
'./Functions/phong',
'./Functions/pointAlongRay',
'./Functions/rayEllipsoidIntersectionInterval',
'./Functions/RGBToXYZ',
'./Functions/saturation',
'./Functions/shadowDepthCompare',
'./Functions/shadowVisibility',
'./Functions/signNotZero',
'./Functions/tangentToEyeSpaceMatrix',
'./Functions/translateRelativeToEye',
'./Functions/translucentPhong',
'./Functions/transpose',
'./Functions/unpackDepth',
'./Functions/windowToEyeCoordinates',
'./Functions/XYZToRGB'
], function(
czm_degreesPerRadian,
czm_depthRange,
czm_epsilon1,
czm_epsilon2,
czm_epsilon3,
czm_epsilon4,
czm_epsilon5,
czm_epsilon6,
czm_epsilon7,
czm_infinity,
czm_oneOverPi,
czm_oneOverTwoPi,
czm_passCompute,
czm_passEnvironment,
czm_passGlobe,
czm_passGround,
czm_passOpaque,
czm_passOverlay,
czm_passTranslucent,
czm_pi,
czm_piOverFour,
czm_piOverSix,
czm_piOverThree,
czm_piOverTwo,
czm_radiansPerDegree,
czm_sceneMode2D,
czm_sceneMode3D,
czm_sceneModeColumbusView,
czm_sceneModeMorphing,
czm_solarRadius,
czm_threePiOver2,
czm_twoPi,
czm_webMercatorMaxLatitude,
czm_depthRangeStruct,
czm_ellipsoid,
czm_material,
czm_materialInput,
czm_ray,
czm_raySegment,
czm_shadowParameters,
czm_alphaWeight,
czm_antialias,
czm_cascadeColor,
czm_cascadeDistance,
czm_cascadeMatrix,
czm_cascadeWeights,
czm_columbusViewMorph,
czm_computePosition,
czm_cosineAndSine,
czm_decompressTextureCoordinates,
czm_eastNorthUpToEyeCoordinates,
czm_ellipsoidContainsPoint,
czm_ellipsoidNew,
czm_ellipsoidWgs84TextureCoordinates,
czm_equalsEpsilon,
czm_eyeOffset,
czm_eyeToWindowCoordinates,
czm_fog,
czm_geodeticSurfaceNormal,
czm_getDefaultMaterial,
czm_getLambertDiffuse,
czm_getSpecular,
czm_getWaterNoise,
czm_getWgs84EllipsoidEC,
czm_hue,
czm_isEmpty,
czm_isFull,
czm_latitudeToWebMercatorFraction,
czm_luminance,
czm_metersPerPixel,
czm_modelToWindowCoordinates,
czm_multiplyWithColorBalance,
czm_nearFarScalar,
czm_octDecode,
czm_packDepth,
czm_phong,
czm_pointAlongRay,
czm_rayEllipsoidIntersectionInterval,
czm_RGBToXYZ,
czm_saturation,
czm_shadowDepthCompare,
czm_shadowVisibility,
czm_signNotZero,
czm_tangentToEyeSpaceMatrix,
czm_translateRelativeToEye,
czm_translucentPhong,
czm_transpose,
czm_unpackDepth,
czm_windowToEyeCoordinates,
czm_XYZToRGB) {
'use strict';
return {
czm_degreesPerRadian : czm_degreesPerRadian,
czm_depthRange : czm_depthRange,
czm_epsilon1 : czm_epsilon1,
czm_epsilon2 : czm_epsilon2,
czm_epsilon3 : czm_epsilon3,
czm_epsilon4 : czm_epsilon4,
czm_epsilon5 : czm_epsilon5,
czm_epsilon6 : czm_epsilon6,
czm_epsilon7 : czm_epsilon7,
czm_infinity : czm_infinity,
czm_oneOverPi : czm_oneOverPi,
czm_oneOverTwoPi : czm_oneOverTwoPi,
czm_passCompute : czm_passCompute,
czm_passEnvironment : czm_passEnvironment,
czm_passGlobe : czm_passGlobe,
czm_passGround : czm_passGround,
czm_passOpaque : czm_passOpaque,
czm_passOverlay : czm_passOverlay,
czm_passTranslucent : czm_passTranslucent,
czm_pi : czm_pi,
czm_piOverFour : czm_piOverFour,
czm_piOverSix : czm_piOverSix,
czm_piOverThree : czm_piOverThree,
czm_piOverTwo : czm_piOverTwo,
czm_radiansPerDegree : czm_radiansPerDegree,
czm_sceneMode2D : czm_sceneMode2D,
czm_sceneMode3D : czm_sceneMode3D,
czm_sceneModeColumbusView : czm_sceneModeColumbusView,
czm_sceneModeMorphing : czm_sceneModeMorphing,
czm_solarRadius : czm_solarRadius,
czm_threePiOver2 : czm_threePiOver2,
czm_twoPi : czm_twoPi,
czm_webMercatorMaxLatitude : czm_webMercatorMaxLatitude,
czm_depthRangeStruct : czm_depthRangeStruct,
czm_ellipsoid : czm_ellipsoid,
czm_material : czm_material,
czm_materialInput : czm_materialInput,
czm_ray : czm_ray,
czm_raySegment : czm_raySegment,
czm_shadowParameters : czm_shadowParameters,
czm_alphaWeight : czm_alphaWeight,
czm_antialias : czm_antialias,
czm_cascadeColor : czm_cascadeColor,
czm_cascadeDistance : czm_cascadeDistance,
czm_cascadeMatrix : czm_cascadeMatrix,
czm_cascadeWeights : czm_cascadeWeights,
czm_columbusViewMorph : czm_columbusViewMorph,
czm_computePosition : czm_computePosition,
czm_cosineAndSine : czm_cosineAndSine,
czm_decompressTextureCoordinates : czm_decompressTextureCoordinates,
czm_eastNorthUpToEyeCoordinates : czm_eastNorthUpToEyeCoordinates,
czm_ellipsoidContainsPoint : czm_ellipsoidContainsPoint,
czm_ellipsoidNew : czm_ellipsoidNew,
czm_ellipsoidWgs84TextureCoordinates : czm_ellipsoidWgs84TextureCoordinates,
czm_equalsEpsilon : czm_equalsEpsilon,
czm_eyeOffset : czm_eyeOffset,
czm_eyeToWindowCoordinates : czm_eyeToWindowCoordinates,
czm_fog : czm_fog,
czm_geodeticSurfaceNormal : czm_geodeticSurfaceNormal,
czm_getDefaultMaterial : czm_getDefaultMaterial,
czm_getLambertDiffuse : czm_getLambertDiffuse,
czm_getSpecular : czm_getSpecular,
czm_getWaterNoise : czm_getWaterNoise,
czm_getWgs84EllipsoidEC : czm_getWgs84EllipsoidEC,
czm_hue : czm_hue,
czm_isEmpty : czm_isEmpty,
czm_isFull : czm_isFull,
czm_latitudeToWebMercatorFraction : czm_latitudeToWebMercatorFraction,
czm_luminance : czm_luminance,
czm_metersPerPixel : czm_metersPerPixel,
czm_modelToWindowCoordinates : czm_modelToWindowCoordinates,
czm_multiplyWithColorBalance : czm_multiplyWithColorBalance,
czm_nearFarScalar : czm_nearFarScalar,
czm_octDecode : czm_octDecode,
czm_packDepth : czm_packDepth,
czm_phong : czm_phong,
czm_pointAlongRay : czm_pointAlongRay,
czm_rayEllipsoidIntersectionInterval : czm_rayEllipsoidIntersectionInterval,
czm_RGBToXYZ : czm_RGBToXYZ,
czm_saturation : czm_saturation,
czm_shadowDepthCompare : czm_shadowDepthCompare,
czm_shadowVisibility : czm_shadowVisibility,
czm_signNotZero : czm_signNotZero,
czm_tangentToEyeSpaceMatrix : czm_tangentToEyeSpaceMatrix,
czm_translateRelativeToEye : czm_translateRelativeToEye,
czm_translucentPhong : czm_translucentPhong,
czm_transpose : czm_transpose,
czm_unpackDepth : czm_unpackDepth,
czm_windowToEyeCoordinates : czm_windowToEyeCoordinates,
czm_XYZToRGB : czm_XYZToRGB};
});
/*global define*/
define('Renderer/ShaderSource',[
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Shaders/Builtin/CzmBuiltins',
'./AutomaticUniforms'
], function(
defaultValue,
defined,
DeveloperError,
CzmBuiltins,
AutomaticUniforms) {
'use strict';
function removeComments(source) {
// remove inline comments
source = source.replace(/\/\/.*/g, '');
// remove multiline comment block
return source.replace(/\/\*\*[\s\S]*?\*\//gm, function(match) {
// preserve the number of lines in the comment block so the line numbers will be correct when debugging shaders
var numberOfLines = match.match(/\n/gm).length;
var replacement = '';
for (var lineNumber = 0; lineNumber < numberOfLines; ++lineNumber) {
replacement += '\n';
}
return replacement;
});
}
function getDependencyNode(name, glslSource, nodes) {
var dependencyNode;
// check if already loaded
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i].name === name) {
dependencyNode = nodes[i];
}
}
if (!defined(dependencyNode)) {
// strip doc comments so we don't accidentally try to determine a dependency for something found
// in a comment
glslSource = removeComments(glslSource);
// create new node
dependencyNode = {
name : name,
glslSource : glslSource,
dependsOn : [],
requiredBy : [],
evaluated : false
};
nodes.push(dependencyNode);
}
return dependencyNode;
}
function generateDependencies(currentNode, dependencyNodes) {
if (currentNode.evaluated) {
return;
}
currentNode.evaluated = true;
// identify all dependencies that are referenced from this glsl source code
var czmMatches = currentNode.glslSource.match(/\bczm_[a-zA-Z0-9_]*/g);
if (defined(czmMatches) && czmMatches !== null) {
// remove duplicates
czmMatches = czmMatches.filter(function(elem, pos) {
return czmMatches.indexOf(elem) === pos;
});
czmMatches.forEach(function(element) {
if (element !== currentNode.name && ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)) {
var referencedNode = getDependencyNode(element, ShaderSource._czmBuiltinsAndUniforms[element], dependencyNodes);
currentNode.dependsOn.push(referencedNode);
referencedNode.requiredBy.push(currentNode);
// recursive call to find any dependencies of the new node
generateDependencies(referencedNode, dependencyNodes);
}
});
}
}
function sortDependencies(dependencyNodes) {
var nodesWithoutIncomingEdges = [];
var allNodes = [];
while (dependencyNodes.length > 0) {
var node = dependencyNodes.pop();
allNodes.push(node);
if (node.requiredBy.length === 0) {
nodesWithoutIncomingEdges.push(node);
}
}
while (nodesWithoutIncomingEdges.length > 0) {
var currentNode = nodesWithoutIncomingEdges.shift();
dependencyNodes.push(currentNode);
for (var i = 0; i < currentNode.dependsOn.length; ++i) {
// remove the edge from the graph
var referencedNode = currentNode.dependsOn[i];
var index = referencedNode.requiredBy.indexOf(currentNode);
referencedNode.requiredBy.splice(index, 1);
// if referenced node has no more incoming edges, add to list
if (referencedNode.requiredBy.length === 0) {
nodesWithoutIncomingEdges.push(referencedNode);
}
}
}
// if there are any nodes left with incoming edges, then there was a circular dependency somewhere in the graph
var badNodes = [];
for (var j = 0; j < allNodes.length; ++j) {
if (allNodes[j].requiredBy.length !== 0) {
badNodes.push(allNodes[j]);
}
}
if (badNodes.length !== 0) {
var message = 'A circular dependency was found in the following built-in functions/structs/constants: \n';
for (j = 0; j < badNodes.length; ++j) {
message = message + badNodes[j].name + '\n';
}
throw new DeveloperError(message);
}
}
function getBuiltinsAndAutomaticUniforms(shaderSource) {
// generate a dependency graph for builtin functions
var dependencyNodes = [];
var root = getDependencyNode('main', shaderSource, dependencyNodes);
generateDependencies(root, dependencyNodes);
sortDependencies(dependencyNodes);
// Concatenate the source code for the function dependencies.
// Iterate in reverse so that dependent items are declared before they are used.
var builtinsSource = '';
for (var i = dependencyNodes.length - 1; i >= 0; --i) {
builtinsSource = builtinsSource + dependencyNodes[i].glslSource + '\n';
}
return builtinsSource.replace(root.glslSource, '');
}
function combineShader(shaderSource, isFragmentShader) {
var i;
var length;
// Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
var combinedSources = '';
var sources = shaderSource.sources;
if (defined(sources)) {
for (i = 0, length = sources.length; i < length; ++i) {
// #line needs to be on its own line.
combinedSources += '\n#line 0\n' + sources[i];
}
}
combinedSources = removeComments(combinedSources);
// Extract existing shader version from sources
var version;
combinedSources = combinedSources.replace(/#version\s+(.*?)\n/gm, function(match, group1) {
if (defined(version) && version !== group1) {
throw new DeveloperError('inconsistent versions found: ' + version + ' and ' + group1);
}
// Extract #version to put at the top
version = group1;
// Replace original #version directive with a new line so the line numbers
// are not off by one. There can be only one #version directive
// and it must appear at the top of the source, only preceded by
// whitespace and comments.
return '\n';
});
// Remove precision qualifier
combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, '');
// Replace main() for picked if desired.
var pickColorQualifier = shaderSource.pickColorQualifier;
if (defined(pickColorQualifier)) {
combinedSources = ShaderSource.createPickFragmentShaderSource(combinedSources, pickColorQualifier);
}
// combine into single string
var result = '';
// #version must be first
// defaults to #version 100 if not specified
if (defined(version)) {
result = '#version ' + version;
}
if (isFragmentShader) {
result += '\
#ifdef GL_FRAGMENT_PRECISION_HIGH\n\
precision highp float;\n\
#else\n\
precision mediump float;\n\
#endif\n\n';
}
// Prepend #defines for uber-shaders
var defines = shaderSource.defines;
if (defined(defines)) {
for (i = 0, length = defines.length; i < length; ++i) {
var define = defines[i];
if (define.length !== 0) {
result += '#define ' + define + '\n';
}
}
}
// append built-ins
if (shaderSource.includeBuiltIns) {
result += getBuiltinsAndAutomaticUniforms(combinedSources);
}
// reset line number
result += '\n#line 0\n';
// append actual source
result += combinedSources;
return result;
}
/**
* An object containing various inputs that will be combined to form a final GLSL shader string.
*
* @param {Object} [options] Object with the following properties:
* @param {String[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
* @param {String[]} [options.defines] An array of strings containing GLSL identifiers to #define
.
* @param {String} [options.pickColorQualifier] The GLSL qualifier, uniform
or varying
, for the input czm_pickColor
. When defined, a pick fragment shader is generated.
* @param {Boolean} [options.includeBuiltIns=true] If true, referenced built-in functions will be included with the combined shader. Set to false if this shader will become a source in another shader, to avoid duplicating functions.
*
* @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'varying'.
*
* @example
* // 1. Prepend #defines to a shader
* var source = new Cesium.ShaderSource({
* defines : ['WHITE'],
* sources : ['void main() { \n#ifdef WHITE\n gl_FragColor = vec4(1.0); \n#else\n gl_FragColor = vec4(0.0); \n#endif\n }']
* });
*
* // 2. Modify a fragment shader for picking
* var source = new Cesium.ShaderSource({
* sources : ['void main() { gl_FragColor = vec4(1.0); }'],
* pickColorQualifier : 'uniform'
* });
*
* @private
*/
function ShaderSource(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var pickColorQualifier = options.pickColorQualifier;
if (defined(pickColorQualifier) && pickColorQualifier !== 'uniform' && pickColorQualifier !== 'varying') {
throw new DeveloperError('options.pickColorQualifier must be \'uniform\' or \'varying\'.');
}
this.defines = defined(options.defines) ? options.defines.slice(0) : [];
this.sources = defined(options.sources) ? options.sources.slice(0) : [];
this.pickColorQualifier = pickColorQualifier;
this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
}
ShaderSource.prototype.clone = function() {
return new ShaderSource({
sources : this.sources,
defines : this.defines,
pickColorQuantifier : this.pickColorQualifier,
includeBuiltIns : this.includeBuiltIns
});
};
ShaderSource.replaceMain = function(source, renamedMain) {
renamedMain = 'void ' + renamedMain + '()';
return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain);
};
/**
* Create a single string containing the full, combined vertex shader with all dependencies and defines.
*
* @returns {String} The combined shader string.
*/
ShaderSource.prototype.createCombinedVertexShader = function() {
return combineShader(this, false);
};
/**
* Create a single string containing the full, combined fragment shader with all dependencies and defines.
*
* @returns {String} The combined shader string.
*/
ShaderSource.prototype.createCombinedFragmentShader = function() {
return combineShader(this, true);
};
/**
* For ShaderProgram testing
* @private
*/
ShaderSource._czmBuiltinsAndUniforms = {};
// combine automatic uniforms and Cesium built-ins
for ( var builtinName in CzmBuiltins) {
if (CzmBuiltins.hasOwnProperty(builtinName)) {
ShaderSource._czmBuiltinsAndUniforms[builtinName] = CzmBuiltins[builtinName];
}
}
for ( var uniformName in AutomaticUniforms) {
if (AutomaticUniforms.hasOwnProperty(uniformName)) {
var uniform = AutomaticUniforms[uniformName];
if (typeof uniform.getDeclaration === 'function') {
ShaderSource._czmBuiltinsAndUniforms[uniformName] = uniform.getDeclaration(uniformName);
}
}
}
ShaderSource.createPickVertexShaderSource = function(vertexShaderSource) {
var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_old_main');
var pickMain = 'attribute vec4 pickColor; \n' +
'varying vec4 czm_pickColor; \n' +
'void main() \n' +
'{ \n' +
' czm_old_main(); \n' +
' czm_pickColor = pickColor; \n' +
'}';
return renamedVS + '\n' + pickMain;
};
ShaderSource.createPickFragmentShaderSource = function(fragmentShaderSource, pickColorQualifier) {
var renamedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_old_main');
var pickMain = pickColorQualifier + ' vec4 czm_pickColor; \n' +
'void main() \n' +
'{ \n' +
' czm_old_main(); \n' +
' if (gl_FragColor.a == 0.0) { \n' +
' discard; \n' +
' } \n' +
' gl_FragColor = czm_pickColor; \n' +
'}';
return renamedFS + '\n' + pickMain;
};
ShaderSource.findVarying = function(shaderSource, names) {
var sources = shaderSource.sources;
var namesLength = names.length;
for (var i = 0; i < namesLength; ++i) {
var name = names[i];
var sourcesLength = sources.length;
for (var j = 0; j < sourcesLength; ++j) {
if (sources[j].indexOf(name) !== -1) {
return name;
}
}
}
return undefined;
};
var normalVaryingNames = ['v_normalEC', 'v_normal'];
ShaderSource.findNormalVarying = function(shaderSource) {
return ShaderSource.findVarying(shaderSource, normalVaryingNames);
};
var positionVaryingNames = ['v_positionEC'];
ShaderSource.findPositionVarying = function(shaderSource) {
return ShaderSource.findVarying(shaderSource, positionVaryingNames);
};
return ShaderSource;
});
/*global define*/
define('Renderer/Buffer',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/IndexDatatype',
'../Core/WebGLConstants',
'./BufferUsage'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
IndexDatatype,
WebGLConstants,
BufferUsage) {
'use strict';
/**
* @private
*/
function Buffer(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
if (!defined(options.typedArray) && !defined(options.sizeInBytes)) {
throw new DeveloperError('Either options.sizeInBytes or options.typedArray is required.');
}
if (defined(options.typedArray) && defined(options.sizeInBytes)) {
throw new DeveloperError('Cannot pass in both options.sizeInBytes and options.typedArray.');
}
if (defined(options.typedArray) && !(typeof options.typedArray === 'object' && typeof options.typedArray.byteLength === 'number')) {
throw new DeveloperError('options.typedArray must be a typed array');
}
if (!BufferUsage.validate(options.usage)) {
throw new DeveloperError('usage is invalid.');
}
var gl = options.context._gl;
var bufferTarget = options.bufferTarget;
var typedArray = options.typedArray;
var sizeInBytes = options.sizeInBytes;
var usage = options.usage;
var hasArray = defined(typedArray);
if (hasArray) {
sizeInBytes = typedArray.byteLength;
}
if (sizeInBytes <= 0) {
throw new DeveloperError('Buffer size must be greater than zero.');
}
var buffer = gl.createBuffer();
gl.bindBuffer(bufferTarget, buffer);
gl.bufferData(bufferTarget, hasArray ? typedArray : sizeInBytes, usage);
gl.bindBuffer(bufferTarget, null);
this._gl = gl;
this._bufferTarget = bufferTarget;
this._sizeInBytes = sizeInBytes;
this._usage = usage;
this._buffer = buffer;
this.vertexArrayDestroyable = true;
}
/**
* Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory.
*
* A vertex array defines the actual makeup of a vertex, e.g., positions, normals, texture coordinates,
* etc., by interpreting the raw data in one or more vertex buffers.
*
* @param {Object} options An object containing the following properties:
* @param {Context} options.context The context in which to create the buffer
* @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
* @param {Number} [options.sizeInBytes] A Number
defining the size of the buffer in bytes. Required if options.typedArray is not given.
* @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
* @returns {VertexBuffer} The vertex buffer, ready to be attached to a vertex array.
*
* @exception {DeveloperError} Must specify either or , but not both.
* @exception {DeveloperError} The buffer size must be greater than zero.
* @exception {DeveloperError} Invalid usage
.
*
*
* @example
* // Example 1. Create a dynamic vertex buffer 16 bytes in size.
* var buffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 16,
* usage : BufferUsage.DYNAMIC_DRAW
* });
*
* @example
* // Example 2. Create a dynamic vertex buffer from three floating-point values.
* // The data copied to the vertex buffer is considered raw bytes until it is
* // interpreted as vertices using a vertex array.
* var positionBuffer = buffer.createVertexBuffer({
* context : context,
* typedArray : new Float32Array([0, 0, 0]),
* usage : BufferUsage.STATIC_DRAW
* });
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with ARRAY_BUFFER
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with ARRAY_BUFFER
*/
Buffer.createVertexBuffer = function(options) {
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
return new Buffer({
context: options.context,
bufferTarget: WebGLConstants.ARRAY_BUFFER,
typedArray: options.typedArray,
sizeInBytes: options.sizeInBytes,
usage: options.usage
});
};
/**
* Creates an index buffer, which contains typed indices in GPU-controlled memory.
*
* An index buffer can be attached to a vertex array to select vertices for rendering.
* Context.draw
can render using the entire index buffer or a subset
* of the index buffer defined by an offset and count.
*
* @param {Object} options An object containing the following properties:
* @param {Context} options.context The context in which to create the buffer
* @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
* @param {Number} [options.sizeInBytes] A Number
defining the size of the buffer in bytes. Required if options.typedArray is not given.
* @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
* @param {IndexDatatype} indexDatatype The datatype of indices in the buffer.
* @returns {IndexBuffer} The index buffer, ready to be attached to a vertex array.
*
* @exception {DeveloperError} Must specify either or , but not both.
* @exception {DeveloperError} IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.
* @exception {DeveloperError} The size in bytes must be greater than zero.
* @exception {DeveloperError} Invalid usage
.
* @exception {DeveloperError} Invalid indexDatatype
.
*
*
* @example
* // Example 1. Create a stream index buffer of unsigned shorts that is
* // 16 bytes in size.
* var buffer = Buffer.createIndexBuffer({
* context : context,
* sizeInBytes : 16,
* usage : BufferUsage.STREAM_DRAW,
* indexDatatype : IndexDatatype.UNSIGNED_SHORT
* });
*
* @example
* // Example 2. Create a static index buffer containing three unsigned shorts.
* var buffer = Buffer.createIndexBuffer({
* context : context,
* typedArray : new Uint16Array([0, 1, 2]),
* usage : BufferUsage.STATIC_DRAW,
* indexDatatype : IndexDatatype.UNSIGNED_SHORT
* });
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with ELEMENT_ARRAY_BUFFER
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with ELEMENT_ARRAY_BUFFER
*/
Buffer.createIndexBuffer = function(options) {
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
if (!IndexDatatype.validate(options.indexDatatype)) {
throw new DeveloperError('Invalid indexDatatype.');
}
if ((options.indexDatatype === IndexDatatype.UNSIGNED_INT) && !options.context.elementIndexUint) {
throw new DeveloperError('IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.');
}
var context = options.context;
var indexDatatype = options.indexDatatype;
var bytesPerIndex = IndexDatatype.getSizeInBytes(indexDatatype);
var buffer = new Buffer({
context : context,
bufferTarget : WebGLConstants.ELEMENT_ARRAY_BUFFER,
typedArray : options.typedArray,
sizeInBytes : options.sizeInBytes,
usage : options.usage
});
var numberOfIndices = buffer.sizeInBytes / bytesPerIndex;
defineProperties(buffer, {
indexDatatype: {
get : function() {
return indexDatatype;
}
},
bytesPerIndex : {
get : function() {
return bytesPerIndex;
}
},
numberOfIndices : {
get : function() {
return numberOfIndices;
}
}
});
return buffer;
};
defineProperties(Buffer.prototype, {
sizeInBytes : {
get : function() {
return this._sizeInBytes;
}
},
usage: {
get : function() {
return this._usage;
}
}
});
Buffer.prototype._getBuffer = function() {
return this._buffer;
};
Buffer.prototype.copyFromArrayView = function(arrayView, offsetInBytes) {
offsetInBytes = defaultValue(offsetInBytes, 0);
if (!arrayView) {
throw new DeveloperError('arrayView is required.');
}
if (offsetInBytes + arrayView.byteLength > this._sizeInBytes) {
throw new DeveloperError('This buffer is not large enough.');
}
var gl = this._gl;
var target = this._bufferTarget;
gl.bindBuffer(target, this._buffer);
gl.bufferSubData(target, offsetInBytes, arrayView);
gl.bindBuffer(target, null);
};
Buffer.prototype.isDestroyed = function() {
return false;
};
Buffer.prototype.destroy = function() {
this._gl.deleteBuffer(this._buffer);
return destroyObject(this);
};
return Buffer;
});
/*global define*/
define('Renderer/VertexArray',[
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Geometry',
'../Core/IndexDatatype',
'../Core/Math',
'../Core/RuntimeError',
'./Buffer',
'./BufferUsage',
'./ContextLimits'
], function(
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Geometry,
IndexDatatype,
CesiumMath,
RuntimeError,
Buffer,
BufferUsage,
ContextLimits) {
'use strict';
function addAttribute(attributes, attribute, index, context) {
var hasVertexBuffer = defined(attribute.vertexBuffer);
var hasValue = defined(attribute.value);
var componentsPerAttribute = attribute.value ? attribute.value.length : attribute.componentsPerAttribute;
if (!hasVertexBuffer && !hasValue) {
throw new DeveloperError('attribute must have a vertexBuffer or a value.');
}
if (hasVertexBuffer && hasValue) {
throw new DeveloperError('attribute cannot have both a vertexBuffer and a value. It must have either a vertexBuffer property defining per-vertex data or a value property defining data for all vertices.');
}
if ((componentsPerAttribute !== 1) &&
(componentsPerAttribute !== 2) &&
(componentsPerAttribute !== 3) &&
(componentsPerAttribute !== 4)) {
if (hasValue) {
throw new DeveloperError('attribute.value.length must be in the range [1, 4].');
}
throw new DeveloperError('attribute.componentsPerAttribute must be in the range [1, 4].');
}
if (defined(attribute.componentDatatype) && !ComponentDatatype.validate(attribute.componentDatatype)) {
throw new DeveloperError('attribute must have a valid componentDatatype or not specify it.');
}
if (defined(attribute.strideInBytes) && (attribute.strideInBytes > 255)) {
// WebGL limit. Not in GL ES.
throw new DeveloperError('attribute must have a strideInBytes less than or equal to 255 or not specify it.');
}
if (defined(attribute.instanceDivisor) && (attribute.instanceDivisor > 0) && !context.instancedArrays) {
throw new DeveloperError('instanced arrays is not supported');
}
if (defined(attribute.instanceDivisor) && (attribute.instanceDivisor < 0)) {
throw new DeveloperError('attribute must have an instanceDivisor greater than or equal to zero');
}
if (defined(attribute.instanceDivisor) && hasValue) {
throw new DeveloperError('attribute cannot have have an instanceDivisor if it is not backed by a buffer');
}
if (defined(attribute.instanceDivisor) && (attribute.instanceDivisor > 0) && (attribute.index === 0)) {
throw new DeveloperError('attribute zero cannot have an instanceDivisor greater than 0');
}
// Shallow copy the attribute; we do not want to copy the vertex buffer.
var attr = {
index : defaultValue(attribute.index, index),
enabled : defaultValue(attribute.enabled, true),
vertexBuffer : attribute.vertexBuffer,
value : hasValue ? attribute.value.slice(0) : undefined,
componentsPerAttribute : componentsPerAttribute,
componentDatatype : defaultValue(attribute.componentDatatype, ComponentDatatype.FLOAT),
normalize : defaultValue(attribute.normalize, false),
offsetInBytes : defaultValue(attribute.offsetInBytes, 0),
strideInBytes : defaultValue(attribute.strideInBytes, 0),
instanceDivisor : defaultValue(attribute.instanceDivisor, 0)
};
if (hasVertexBuffer) {
// Common case: vertex buffer for per-vertex data
attr.vertexAttrib = function(gl) {
var index = this.index;
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer._getBuffer());
gl.vertexAttribPointer(index, this.componentsPerAttribute, this.componentDatatype, this.normalize, this.strideInBytes, this.offsetInBytes);
gl.enableVertexAttribArray(index);
if (this.instanceDivisor > 0) {
context.glVertexAttribDivisor(index, this.instanceDivisor);
context._vertexAttribDivisors[index] = this.instanceDivisor;
context._previousDrawInstanced = true;
}
};
attr.disableVertexAttribArray = function(gl) {
gl.disableVertexAttribArray(this.index);
if (this.instanceDivisor > 0) {
context.glVertexAttribDivisor(index, 0);
}
};
} else {
// Less common case: value array for the same data for each vertex
switch (attr.componentsPerAttribute) {
case 1:
attr.vertexAttrib = function(gl) {
gl.vertexAttrib1fv(this.index, this.value);
};
break;
case 2:
attr.vertexAttrib = function(gl) {
gl.vertexAttrib2fv(this.index, this.value);
};
break;
case 3:
attr.vertexAttrib = function(gl) {
gl.vertexAttrib3fv(this.index, this.value);
};
break;
case 4:
attr.vertexAttrib = function(gl) {
gl.vertexAttrib4fv(this.index, this.value);
};
break;
}
attr.disableVertexAttribArray = function(gl) {
};
}
attributes.push(attr);
}
function bind(gl, attributes, indexBuffer) {
for ( var i = 0; i < attributes.length; ++i) {
var attribute = attributes[i];
if (attribute.enabled) {
attribute.vertexAttrib(gl);
}
}
if (defined(indexBuffer)) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._getBuffer());
}
}
/**
* Creates a vertex array, which defines the attributes making up a vertex, and contains an optional index buffer
* to select vertices for rendering. Attributes are defined using object literals as shown in Example 1 below.
*
* @param {Object} options Object with the following properties:
* @param {Context} options.context The context in which the VertexArray gets created.
* @param {Object[]} options.attributes An array of attributes.
* @param {IndexBuffer} [options.indexBuffer] An optional index buffer.
*
* @returns {VertexArray} The vertex array, ready for use with drawing.
*
* @exception {DeveloperError} Attribute must have a vertexBuffer
.
* @exception {DeveloperError} Attribute must have a componentsPerAttribute
.
* @exception {DeveloperError} Attribute must have a valid componentDatatype
or not specify it.
* @exception {DeveloperError} Attribute must have a strideInBytes
less than or equal to 255 or not specify it.
* @exception {DeveloperError} Index n is used by more than one attribute.
*
*
* @example
* // Example 1. Create a vertex array with vertices made up of three floating point
* // values, e.g., a position, from a single vertex buffer. No index buffer is used.
* var positionBuffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* });
* var attributes = [
* {
* index : 0,
* enabled : true,
* vertexBuffer : positionBuffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT,
* normalize : false,
* offsetInBytes : 0,
* strideInBytes : 0 // tightly packed
* instanceDivisor : 0 // not instanced
* }
* ];
* var va = new VertexArray({
* context : context,
* attributes : attributes
* });
*
* @example
* // Example 2. Create a vertex array with vertices from two different vertex buffers.
* // Each vertex has a three-component position and three-component normal.
* var positionBuffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* });
* var normalBuffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 12,
* usage : BufferUsage.STATIC_DRAW
* });
* var attributes = [
* {
* index : 0,
* vertexBuffer : positionBuffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT
* },
* {
* index : 1,
* vertexBuffer : normalBuffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT
* }
* ];
* var va = new VertexArray({
* context : context,
* attributes : attributes
* });
*
* @example
* // Example 3. Creates the same vertex layout as Example 2 using a single
* // vertex buffer, instead of two.
* var buffer = Buffer.createVertexBuffer({
* context : context,
* sizeInBytes : 24,
* usage : BufferUsage.STATIC_DRAW
* });
* var attributes = [
* {
* vertexBuffer : buffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT,
* offsetInBytes : 0,
* strideInBytes : 24
* },
* {
* vertexBuffer : buffer,
* componentsPerAttribute : 3,
* componentDatatype : ComponentDatatype.FLOAT,
* normalize : true,
* offsetInBytes : 12,
* strideInBytes : 24
* }
* ];
* var va = new VertexArray({
* context : context,
* attributes : attributes
* });
*
* @see Buffer#createVertexBuffer
* @see Buffer#createIndexBuffer
* @see Context#draw
*
* @private
*/
function VertexArray(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
if (!defined(options.attributes)) {
throw new DeveloperError('options.attributes is required.');
}
var context = options.context;
var gl = context._gl;
var attributes = options.attributes;
var indexBuffer = options.indexBuffer;
var i;
var vaAttributes = [];
var numberOfVertices = 1; // if every attribute is backed by a single value
var hasInstancedAttributes = false;
var length = attributes.length;
for (i = 0; i < length; ++i) {
addAttribute(vaAttributes, attributes[i], i, context);
}
length = vaAttributes.length;
for (i = 0; i < length; ++i) {
var attribute = vaAttributes[i];
if (defined(attribute.vertexBuffer) && (attribute.instanceDivisor === 0)) {
// This assumes that each vertex buffer in the vertex array has the same number of vertices.
var bytes = attribute.strideInBytes || (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(attribute.componentDatatype));
numberOfVertices = attribute.vertexBuffer.sizeInBytes / bytes;
break;
}
}
for (i = 0; i < length; ++i) {
if (vaAttributes[i].instanceDivisor > 0) {
hasInstancedAttributes = true;
break;
}
}
// Verify all attribute names are unique
var uniqueIndices = {};
for (i = 0; i < length; ++i) {
var index = vaAttributes[i].index;
if (uniqueIndices[index]) {
throw new DeveloperError('Index ' + index + ' is used by more than one attribute.');
}
uniqueIndices[index] = true;
}
var vao;
// Setup VAO if supported
if (context.vertexArrayObject) {
vao = context.glCreateVertexArray();
context.glBindVertexArray(vao);
bind(gl, vaAttributes, indexBuffer);
context.glBindVertexArray(null);
}
this._numberOfVertices = numberOfVertices;
this._hasInstancedAttributes = hasInstancedAttributes;
this._context = context;
this._gl = gl;
this._vao = vao;
this._attributes = vaAttributes;
this._indexBuffer = indexBuffer;
}
function computeNumberOfVertices(attribute) {
return attribute.values.length / attribute.componentsPerAttribute;
}
function computeAttributeSizeInBytes(attribute) {
return ComponentDatatype.getSizeInBytes(attribute.componentDatatype) * attribute.componentsPerAttribute;
}
function interleaveAttributes(attributes) {
var j;
var name;
var attribute;
// Extract attribute names.
var names = [];
for (name in attributes) {
// Attribute needs to have per-vertex values; not a constant value for all vertices.
if (attributes.hasOwnProperty(name) &&
defined(attributes[name]) &&
defined(attributes[name].values)) {
names.push(name);
if (attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
attributes[name].componentDatatype = ComponentDatatype.FLOAT;
attributes[name].values = ComponentDatatype.createTypedArray(ComponentDatatype.FLOAT, attributes[name].values);
}
}
}
// Validation. Compute number of vertices.
var numberOfVertices;
var namesLength = names.length;
if (namesLength > 0) {
numberOfVertices = computeNumberOfVertices(attributes[names[0]]);
for (j = 1; j < namesLength; ++j) {
var currentNumberOfVertices = computeNumberOfVertices(attributes[names[j]]);
if (currentNumberOfVertices !== numberOfVertices) {
throw new RuntimeError(
'Each attribute list must have the same number of vertices. ' +
'Attribute ' + names[j] + ' has a different number of vertices ' +
'(' + currentNumberOfVertices.toString() + ')' +
' than attribute ' + names[0] +
' (' + numberOfVertices.toString() + ').');
}
}
}
// Sort attributes by the size of their components. From left to right, a vertex stores floats, shorts, and then bytes.
names.sort(function(left, right) {
return ComponentDatatype.getSizeInBytes(attributes[right].componentDatatype) - ComponentDatatype.getSizeInBytes(attributes[left].componentDatatype);
});
// Compute sizes and strides.
var vertexSizeInBytes = 0;
var offsetsInBytes = {};
for (j = 0; j < namesLength; ++j) {
name = names[j];
attribute = attributes[name];
offsetsInBytes[name] = vertexSizeInBytes;
vertexSizeInBytes += computeAttributeSizeInBytes(attribute);
}
if (vertexSizeInBytes > 0) {
// Pad each vertex to be a multiple of the largest component datatype so each
// attribute can be addressed using typed arrays.
var maxComponentSizeInBytes = ComponentDatatype.getSizeInBytes(attributes[names[0]].componentDatatype); // Sorted large to small
var remainder = vertexSizeInBytes % maxComponentSizeInBytes;
if (remainder !== 0) {
vertexSizeInBytes += (maxComponentSizeInBytes - remainder);
}
// Total vertex buffer size in bytes, including per-vertex padding.
var vertexBufferSizeInBytes = numberOfVertices * vertexSizeInBytes;
// Create array for interleaved vertices. Each attribute has a different view (pointer) into the array.
var buffer = new ArrayBuffer(vertexBufferSizeInBytes);
var views = {};
for (j = 0; j < namesLength; ++j) {
name = names[j];
var sizeInBytes = ComponentDatatype.getSizeInBytes(attributes[name].componentDatatype);
views[name] = {
pointer : ComponentDatatype.createTypedArray(attributes[name].componentDatatype, buffer),
index : offsetsInBytes[name] / sizeInBytes, // Offset in ComponentType
strideInComponentType : vertexSizeInBytes / sizeInBytes
};
}
// Copy attributes into one interleaved array.
// PERFORMANCE_IDEA: Can we optimize these loops?
for (j = 0; j < numberOfVertices; ++j) {
for ( var n = 0; n < namesLength; ++n) {
name = names[n];
attribute = attributes[name];
var values = attribute.values;
var view = views[name];
var pointer = view.pointer;
var numberOfComponents = attribute.componentsPerAttribute;
for ( var k = 0; k < numberOfComponents; ++k) {
pointer[view.index + k] = values[(j * numberOfComponents) + k];
}
view.index += view.strideInComponentType;
}
}
return {
buffer : buffer,
offsetsInBytes : offsetsInBytes,
vertexSizeInBytes : vertexSizeInBytes
};
}
// No attributes to interleave.
return undefined;
}
/**
* Creates a vertex array from a geometry. A geometry contains vertex attributes and optional index data
* in system memory, whereas a vertex array contains vertex buffers and an optional index buffer in WebGL
* memory for use with rendering.
*
* The geometry
argument should use the standard layout like the geometry returned by {@link BoxGeometry}.
*
* options
can have four properties:
*
* geometry
: The source geometry containing data used to create the vertex array.
* attributeLocations
: An object that maps geometry attribute names to vertex shader attribute locations.
* bufferUsage
: The expected usage pattern of the vertex array's buffers. On some WebGL implementations, this can significantly affect performance. See {@link BufferUsage}. Default: BufferUsage.DYNAMIC_DRAW
.
* interleave
: Determines if all attributes are interleaved in a single vertex buffer or if each attribute is stored in a separate vertex buffer. Default: false
.
*
*
* If options
is not specified or the geometry
contains no data, the returned vertex array is empty.
*
* @param {Object} options An object defining the geometry, attribute indices, buffer usage, and vertex layout used to create the vertex array.
*
* @exception {RuntimeError} Each attribute list must have the same number of vertices.
* @exception {DeveloperError} The geometry must have zero or one index lists.
* @exception {DeveloperError} Index n is used by more than one attribute.
*
*
* @example
* // Example 1. Creates a vertex array for rendering a box. The default dynamic draw
* // usage is used for the created vertex and index buffer. The attributes are not
* // interleaved by default.
* var geometry = new BoxGeometry();
* var va = VertexArray.fromGeometry({
* context : context,
* geometry : geometry,
* attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
* });
*
* @example
* // Example 2. Creates a vertex array with interleaved attributes in a
* // single vertex buffer. The vertex and index buffer have static draw usage.
* var va = VertexArray.fromGeometry({
* context : context,
* geometry : geometry,
* attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
* bufferUsage : BufferUsage.STATIC_DRAW,
* interleave : true
* });
*
* @example
* // Example 3. When the caller destroys the vertex array, it also destroys the
* // attached vertex buffer(s) and index buffer.
* va = va.destroy();
*
* @see Buffer#createVertexBuffer
* @see Buffer#createIndexBuffer
* @see GeometryPipeline.createAttributeLocations
* @see ShaderProgram
*/
VertexArray.fromGeometry = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
var context = options.context;
var geometry = defaultValue(options.geometry, defaultValue.EMPTY_OBJECT);
var bufferUsage = defaultValue(options.bufferUsage, BufferUsage.DYNAMIC_DRAW);
var attributeLocations = defaultValue(options.attributeLocations, defaultValue.EMPTY_OBJECT);
var interleave = defaultValue(options.interleave, false);
var createdVAAttributes = options.vertexArrayAttributes;
var name;
var attribute;
var vertexBuffer;
var vaAttributes = (defined(createdVAAttributes)) ? createdVAAttributes : [];
var attributes = geometry.attributes;
if (interleave) {
// Use a single vertex buffer with interleaved vertices.
var interleavedAttributes = interleaveAttributes(attributes);
if (defined(interleavedAttributes)) {
vertexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : interleavedAttributes.buffer,
usage : bufferUsage
});
var offsetsInBytes = interleavedAttributes.offsetsInBytes;
var strideInBytes = interleavedAttributes.vertexSizeInBytes;
for (name in attributes) {
if (attributes.hasOwnProperty(name) && defined(attributes[name])) {
attribute = attributes[name];
if (defined(attribute.values)) {
// Common case: per-vertex attributes
vaAttributes.push({
index : attributeLocations[name],
vertexBuffer : vertexBuffer,
componentDatatype : attribute.componentDatatype,
componentsPerAttribute : attribute.componentsPerAttribute,
normalize : attribute.normalize,
offsetInBytes : offsetsInBytes[name],
strideInBytes : strideInBytes
});
} else {
// Constant attribute for all vertices
vaAttributes.push({
index : attributeLocations[name],
value : attribute.value,
componentDatatype : attribute.componentDatatype,
normalize : attribute.normalize
});
}
}
}
}
} else {
// One vertex buffer per attribute.
for (name in attributes) {
if (attributes.hasOwnProperty(name) && defined(attributes[name])) {
attribute = attributes[name];
var componentDatatype = attribute.componentDatatype;
if (componentDatatype === ComponentDatatype.DOUBLE) {
componentDatatype = ComponentDatatype.FLOAT;
}
vertexBuffer = undefined;
if (defined(attribute.values)) {
vertexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : ComponentDatatype.createTypedArray(componentDatatype, attribute.values),
usage : bufferUsage
});
}
vaAttributes.push({
index : attributeLocations[name],
vertexBuffer : vertexBuffer,
value : attribute.value,
componentDatatype : componentDatatype,
componentsPerAttribute : attribute.componentsPerAttribute,
normalize : attribute.normalize
});
}
}
}
var indexBuffer;
var indices = geometry.indices;
if (defined(indices)) {
if ((Geometry.computeNumberOfVertices(geometry) >= CesiumMath.SIXTY_FOUR_KILOBYTES) && context.elementIndexUint) {
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : new Uint32Array(indices),
usage : bufferUsage,
indexDatatype : IndexDatatype.UNSIGNED_INT
});
} else{
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : new Uint16Array(indices),
usage : bufferUsage,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
}
}
return new VertexArray({
context : context,
attributes : vaAttributes,
indexBuffer : indexBuffer
});
};
defineProperties(VertexArray.prototype, {
numberOfAttributes : {
get : function() {
return this._attributes.length;
}
},
numberOfVertices : {
get : function() {
return this._numberOfVertices;
}
},
indexBuffer : {
get : function() {
return this._indexBuffer;
}
}
});
/**
* index is the location in the array of attributes, not the index property of an attribute.
*/
VertexArray.prototype.getAttribute = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._attributes[index];
};
// Workaround for ANGLE, where the attribute divisor seems to be part of the global state instead
// of the VAO state. This function is called when the vao is bound, and should be removed
// once the ANGLE issue is resolved. Setting the divisor should normally happen in vertexAttrib and
// disableVertexAttribArray.
function setVertexAttribDivisor(vertexArray) {
var context = vertexArray._context;
var hasInstancedAttributes = vertexArray._hasInstancedAttributes;
if (!hasInstancedAttributes && !context._previousDrawInstanced) {
return;
}
context._previousDrawInstanced = hasInstancedAttributes;
var divisors = context._vertexAttribDivisors;
var attributes = vertexArray._attributes;
var maxAttributes = ContextLimits.maximumVertexAttributes;
var i;
if (hasInstancedAttributes) {
var length = attributes.length;
for (i = 0; i < length; ++i) {
var attribute = attributes[i];
if (attribute.enabled) {
var divisor = attribute.instanceDivisor;
var index = attribute.index;
if (divisor !== divisors[index]) {
context.glVertexAttribDivisor(index, divisor);
divisors[index] = divisor;
}
}
}
} else {
for (i = 0; i < maxAttributes; ++i) {
if (divisors[i] > 0) {
context.glVertexAttribDivisor(i, 0);
divisors[i] = 0;
}
}
}
}
VertexArray.prototype._bind = function() {
if (defined(this._vao)) {
this._context.glBindVertexArray(this._vao);
if (this._context.instancedArrays) {
setVertexAttribDivisor(this);
}
} else {
bind(this._gl, this._attributes, this._indexBuffer);
}
};
VertexArray.prototype._unBind = function() {
if (defined(this._vao)) {
this._context.glBindVertexArray(null);
} else {
var attributes = this._attributes;
var gl = this._gl;
for ( var i = 0; i < attributes.length; ++i) {
var attribute = attributes[i];
if (attribute.enabled) {
attribute.disableVertexAttribArray(gl);
}
}
if (this._indexBuffer) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
}
};
VertexArray.prototype.isDestroyed = function() {
return false;
};
VertexArray.prototype.destroy = function() {
var attributes = this._attributes;
for ( var i = 0; i < attributes.length; ++i) {
var vertexBuffer = attributes[i].vertexBuffer;
if (defined(vertexBuffer) && !vertexBuffer.isDestroyed() && vertexBuffer.vertexArrayDestroyable) {
vertexBuffer.destroy();
}
}
var indexBuffer = this._indexBuffer;
if (defined(indexBuffer) && !indexBuffer.isDestroyed() && indexBuffer.vertexArrayDestroyable) {
indexBuffer.destroy();
}
if (defined(this._vao)) {
this._context.glDeleteVertexArray(this._vao);
}
return destroyObject(this);
};
return VertexArray;
});
/*global define*/
define('Scene/BatchTable',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/combine',
'../Core/ComponentDatatype',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Math',
'../Core/PixelFormat',
'../Core/RuntimeError',
'../Renderer/ContextLimits',
'../Renderer/PixelDatatype',
'../Renderer/Sampler',
'../Renderer/Texture',
'../Renderer/TextureMagnificationFilter',
'../Renderer/TextureMinificationFilter'
], function(
Cartesian2,
Cartesian3,
Cartesian4,
combine,
ComponentDatatype,
defined,
defineProperties,
destroyObject,
DeveloperError,
CesiumMath,
PixelFormat,
RuntimeError,
ContextLimits,
PixelDatatype,
Sampler,
Texture,
TextureMagnificationFilter,
TextureMinificationFilter) {
'use strict';
/**
* Creates a texture to look up per instance attributes for batched primitives. For example, store each primitive's pick color in the texture.
*
* @alias BatchTable
* @constructor
* @private
*
* @param {Context} context The context in which the batch table is created.
* @param {Object[]} attributes An array of objects describing a per instance attribute. Each object contains a datatype, components per attributes, whether it is normalized and a function name
* to retrieve the value in the vertex shader.
* @param {Number} numberOfInstances The number of instances in a batch table.
*
* @example
* // create the batch table
* var attributes = [{
* functionName : 'getShow',
* componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
* componentsPerAttribute : 1
* }, {
* functionName : 'getPickColor',
* componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
* componentsPerAttribute : 4,
* normalize : true
* }];
* var batchTable = new BatchTable(context, attributes, 5);
*
* // when creating the draw commands, update the uniform map and the vertex shader
* vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource);
* var shaderProgram = ShaderProgram.fromCache({
* // ...
* vertexShaderSource : vertexShaderSource,
* });
*
* drawCommand.shaderProgram = shaderProgram;
* drawCommand.uniformMap = batchTable.getUniformMapCallback()(uniformMap);
*
* // use the attribute function names in the shader to retrieve the instance values
* // ...
* attribute float batchId;
*
* void main() {
* // ...
* float show = getShow(batchId);
* vec3 pickColor = getPickColor(batchId);
* // ...
* }
*/
function BatchTable(context, attributes, numberOfInstances) {
if (!defined(context)) {
throw new DeveloperError('context is required');
}
if (!defined(attributes)) {
throw new DeveloperError('attributes is required');
}
if (!defined(numberOfInstances)) {
throw new DeveloperError('numberOfInstances is required');
}
this._attributes = attributes;
this._numberOfInstances = numberOfInstances;
if (attributes.length === 0) {
return;
}
// PERFORMANCE_IDEA: We may be able to arrange the attributes so they can be packing into fewer texels.
// Right now, an attribute with one component uses an entire texel when 4 single component attributes can
// be packed into a texel.
//
// Packing floats into unsigned byte textures makes the problem worse. A single component float attribute
// will be packed into a single texel leaving 3 texels unused. 4 texels are reserved for each float attribute
// regardless of how many components it has.
var pixelDatatype = getDatatype(attributes);
var textureFloatSupported = context.floatingPointTexture;
var packFloats = pixelDatatype === PixelDatatype.FLOAT && !textureFloatSupported;
var offsets = createOffsets(attributes, packFloats);
var stride = getStride(offsets, attributes, packFloats);
var maxNumberOfInstancesPerRow = Math.floor(ContextLimits.maximumTextureSize / stride);
var instancesPerWidth = Math.min(numberOfInstances, maxNumberOfInstancesPerRow);
var width = stride * instancesPerWidth;
var height = Math.ceil(numberOfInstances / instancesPerWidth);
var stepX = 1.0 / width;
var centerX = stepX * 0.5;
var stepY = 1.0 / height;
var centerY = stepY * 0.5;
this._textureDimensions = new Cartesian2(width, height);
this._textureStep = new Cartesian4(stepX, centerX, stepY, centerY);
this._pixelDatatype = !packFloats ? pixelDatatype : PixelDatatype.UNSIGNED_BYTE;
this._packFloats = packFloats;
this._offsets = offsets;
this._stride = stride;
this._texture = undefined;
var batchLength = 4 * width * height;
this._batchValues = pixelDatatype === PixelDatatype.FLOAT && !packFloats ? new Float32Array(batchLength) : new Uint8Array(batchLength);
this._batchValuesDirty = false;
}
defineProperties(BatchTable.prototype, {
/**
* The attribute descriptions.
* @memberOf BatchTable.prototype
* @type {Object[]}
* @readonly
*/
attributes : {
get : function() {
return this._attributes;
}
},
/**
* The number of instances.
* @memberOf BatchTable.prototype
* @type {Number}
* @readonly
*/
numberOfInstances : {
get : function () {
return this._numberOfInstances;
}
}
});
function getDatatype(attributes) {
var foundFloatDatatype = false;
var length = attributes.length;
for (var i = 0; i < length; ++i) {
if (attributes[i].componentDatatype !== ComponentDatatype.UNSIGNED_BYTE) {
foundFloatDatatype = true;
break;
}
}
return foundFloatDatatype ? PixelDatatype.FLOAT : PixelDatatype.UNSIGNED_BYTE;
}
function getAttributeType(attributes, attributeIndex) {
var componentsPerAttribute = attributes[attributeIndex].componentsPerAttribute;
if (componentsPerAttribute === 2) {
return Cartesian2;
} else if (componentsPerAttribute === 3) {
return Cartesian3;
} else if (componentsPerAttribute === 4) {
return Cartesian4;
}
return Number;
}
function createOffsets(attributes, packFloats) {
var offsets = new Array(attributes.length);
var currentOffset = 0;
var attributesLength = attributes.length;
for (var i = 0; i < attributesLength; ++i) {
var attribute = attributes[i];
var componentDatatype = attribute.componentDatatype;
offsets[i] = currentOffset;
if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
currentOffset += 4;
} else {
++currentOffset;
}
}
return offsets;
}
function getStride(offsets, attributes, packFloats) {
var length = offsets.length;
var lastOffset = offsets[length - 1];
var lastAttribute = attributes[length - 1];
var componentDatatype = lastAttribute.componentDatatype;
if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
return lastOffset + 4;
}
return lastOffset + 1;
}
var scratchPackedFloatCartesian4 = new Cartesian4();
var SHIFT_LEFT_8 = 256.0;
var SHIFT_LEFT_16 = 65536.0;
var SHIFT_LEFT_24 = 16777216.0;
var SHIFT_RIGHT_8 = 1.0 / SHIFT_LEFT_8;
var SHIFT_RIGHT_16 = 1.0 / SHIFT_LEFT_16;
var SHIFT_RIGHT_24 = 1.0 / SHIFT_LEFT_24;
var BIAS = 38.0;
function unpackFloat(value) {
var temp = value.w / 2.0;
var exponent = Math.floor(temp);
var sign = (temp - exponent) * 2.0;
exponent = exponent - BIAS;
sign = sign * 2.0 - 1.0;
sign = -sign;
if (exponent >= BIAS) {
return sign < 0.0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
}
var unpacked = sign * value.x * SHIFT_RIGHT_8;
unpacked += sign * value.y * SHIFT_RIGHT_16;
unpacked += sign * value.z * SHIFT_RIGHT_24;
return unpacked * Math.pow(10.0, exponent);
}
function getPackedFloat(array, index, result) {
var packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4);
var x = unpackFloat(packed);
packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4);
var y = unpackFloat(packed);
packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4);
var z = unpackFloat(packed);
packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4);
var w = unpackFloat(packed);
return Cartesian4.fromElements(x, y, z, w, result);
}
var scratchFloatArray = new Float32Array(1);
function packFloat(value, result) {
scratchFloatArray[0] = value;
value = scratchFloatArray[0];
if (value === 0.0) {
return Cartesian4.clone(Cartesian4.ZERO, result);
}
var sign = value < 0.0 ? 1.0 : 0.0;
var exponent;
if (!isFinite(value)) {
value = 0.1;
exponent = BIAS;
} else {
value = Math.abs(value);
exponent = Math.floor(CesiumMath.logBase(value, 10)) + 1.0;
value = value / Math.pow(10.0, exponent);
}
var temp = value * SHIFT_LEFT_8;
result.x = Math.floor(temp);
temp = (temp - result.x) * SHIFT_LEFT_8;
result.y = Math.floor(temp);
temp = (temp - result.y) * SHIFT_LEFT_8;
result.z = Math.floor(temp);
result.w = (exponent + BIAS) * 2.0 + sign;
return result;
}
function setPackedAttribute(value, array, index) {
var packed = packFloat(value.x, scratchPackedFloatCartesian4);
Cartesian4.pack(packed, array, index);
packed = packFloat(value.y, packed);
Cartesian4.pack(packed, array, index + 4);
packed = packFloat(value.z, packed);
Cartesian4.pack(packed, array, index + 8);
packed = packFloat(value.w, packed);
Cartesian4.pack(packed, array, index + 12);
}
var scratchGetAttributeCartesian4 = new Cartesian4();
/**
* Gets the value of an attribute in the table.
*
* @param {Number} instanceIndex The index of the instance.
* @param {Number} attributeIndex The index of the attribute.
* @param {undefined|Cartesian2|Cartesian3|Cartesian4} [result] The object onto which to store the result. The type is dependent on the attribute's number of components.
* @returns {Number|Cartesian2|Cartesian3|Cartesian4} The attribute value stored for the instance.
*
* @exception {DeveloperError} instanceIndex is out of range.
* @exception {DeveloperError} attributeIndex is out of range.
*/
BatchTable.prototype.getBatchedAttribute = function(instanceIndex, attributeIndex, result) {
if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
throw new DeveloperError('instanceIndex is out of range.');
}
if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
throw new DeveloperError('attributeIndex is out of range');
}
var attributes = this._attributes;
var offset = this._offsets[attributeIndex];
var stride = this._stride;
var index = 4 * stride * instanceIndex + 4 * offset;
var value;
if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) {
value = getPackedFloat(this._batchValues, index, scratchGetAttributeCartesian4);
} else {
value = Cartesian4.unpack(this._batchValues, index, scratchGetAttributeCartesian4);
}
var attributeType = getAttributeType(attributes, attributeIndex);
if (defined(attributeType.fromCartesian4)) {
return attributeType.fromCartesian4(value, result);
} else if (defined(attributeType.clone)) {
return attributeType.clone(value, result);
}
return value.x;
};
var setAttributeScratchValues = [undefined, undefined, new Cartesian2(), new Cartesian3(), new Cartesian4()];
var setAttributeScratchCartesian4 = new Cartesian4();
/**
* Sets the value of an attribute in the table.
*
* @param {Number} instanceIndex The index of the instance.
* @param {Number} attributeIndex The index of the attribute.
* @param {Number|Cartesian2|Cartesian3|Cartesian4} value The value to be stored in the table. The type of value will depend on the number of components of the attribute.
*
* @exception {DeveloperError} instanceIndex is out of range.
* @exception {DeveloperError} attributeIndex is out of range.
*/
BatchTable.prototype.setBatchedAttribute = function(instanceIndex, attributeIndex, value) {
if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
throw new DeveloperError('instanceIndex is out of range.');
}
if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
throw new DeveloperError('attributeIndex is out of range');
}
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var attributes = this._attributes;
var result = setAttributeScratchValues[attributes[attributeIndex].componentsPerAttribute];
var currentAttribute = this.getBatchedAttribute(instanceIndex, attributeIndex, result);
var attributeType = getAttributeType(this._attributes, attributeIndex);
var entriesEqual = defined(attributeType.equals) ? attributeType.equals(currentAttribute, value) : currentAttribute === value;
if (entriesEqual) {
return;
}
var attributeValue = setAttributeScratchCartesian4;
attributeValue.x = defined(value.x) ? value.x : value;
attributeValue.y = defined(value.y) ? value.y : 0.0;
attributeValue.z = defined(value.z) ? value.z : 0.0;
attributeValue.w = defined(value.w) ? value.w : 0.0;
var offset = this._offsets[attributeIndex];
var stride = this._stride;
var index = 4 * stride * instanceIndex + 4 * offset;
if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) {
setPackedAttribute(attributeValue, this._batchValues, index);
} else {
Cartesian4.pack(attributeValue, this._batchValues, index);
}
this._batchValuesDirty = true;
};
function createTexture(batchTable, context) {
var dimensions = batchTable._textureDimensions;
batchTable._texture = new Texture({
context : context,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : batchTable._pixelDatatype,
width : dimensions.x,
height : dimensions.y,
sampler : new Sampler({
minificationFilter : TextureMinificationFilter.NEAREST,
magnificationFilter : TextureMagnificationFilter.NEAREST
})
});
}
function updateTexture(batchTable) {
var dimensions = batchTable._textureDimensions;
batchTable._texture.copyFrom({
width : dimensions.x,
height : dimensions.y,
arrayBufferView : batchTable._batchValues
});
}
/**
* Creates/updates the batch table texture.
* @param {FrameState} frameState The frame state.
*
* @exception {RuntimeError} The floating point texture extension is required but not supported.
*/
BatchTable.prototype.update = function(frameState) {
if ((defined(this._texture) && !this._batchValuesDirty) || this._attributes.length === 0) {
return;
}
this._batchValuesDirty = false;
if (!defined(this._texture)) {
createTexture(this, frameState.context);
}
updateTexture(this);
};
/**
* Gets a function that will update a uniform map to contain values for looking up values in the batch table.
*
* @returns {BatchTable~updateUniformMapCallback} A callback for updating uniform maps.
*/
BatchTable.prototype.getUniformMapCallback = function() {
var that = this;
return function(uniformMap) {
if (that._attributes.length === 0) {
return uniformMap;
}
var batchUniformMap = {
batchTexture : function() {
return that._texture;
},
batchTextureDimensions : function() {
return that._textureDimensions;
},
batchTextureStep : function() {
return that._textureStep;
}
};
return combine(uniformMap, batchUniformMap);
};
};
function getGlslComputeSt(batchTable) {
var stride = batchTable._stride;
// GLSL batchId is zero-based: [0, numberOfInstances - 1]
if (batchTable._textureDimensions.y === 1) {
return 'uniform vec4 batchTextureStep; \n' +
'vec2 computeSt(float batchId) \n' +
'{ \n' +
' float stepX = batchTextureStep.x; \n' +
' float centerX = batchTextureStep.y; \n' +
' float numberOfAttributes = float('+ stride + '); \n' +
' return vec2(centerX + (batchId * numberOfAttributes * stepX), 0.5); \n' +
'} \n';
}
return 'uniform vec4 batchTextureStep; \n' +
'uniform vec2 batchTextureDimensions; \n' +
'vec2 computeSt(float batchId) \n' +
'{ \n' +
' float stepX = batchTextureStep.x; \n' +
' float centerX = batchTextureStep.y; \n' +
' float stepY = batchTextureStep.z; \n' +
' float centerY = batchTextureStep.w; \n' +
' float numberOfAttributes = float('+ stride + '); \n' +
' float xId = mod(batchId * numberOfAttributes, batchTextureDimensions.x); \n' +
' float yId = floor(batchId * numberOfAttributes / batchTextureDimensions.x); \n' +
' return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' +
'} \n';
}
function getGlslUnpackFloat(batchTable) {
if (!batchTable._packFloats) {
return '';
}
return 'float unpackFloat(vec4 value) \n' +
'{ \n' +
' value *= 255.0; \n' +
' float temp = value.w / 2.0; \n' +
' float exponent = floor(temp); \n' +
' float sign = (temp - exponent) * 2.0; \n' +
' exponent = exponent - float(' + BIAS + '); \n' +
' sign = sign * 2.0 - 1.0; \n' +
' sign = -sign; \n' +
' float unpacked = sign * value.x * float(' + SHIFT_RIGHT_8 + '); \n' +
' unpacked += sign * value.y * float(' + SHIFT_RIGHT_16 + '); \n' +
' unpacked += sign * value.z * float(' + SHIFT_RIGHT_24 + '); \n' +
' return unpacked * pow(10.0, exponent); \n' +
'} \n';
}
function getComponentType(componentsPerAttribute) {
if (componentsPerAttribute === 1) {
return 'float';
}
return 'vec' + componentsPerAttribute;
}
function getComponentSwizzle(componentsPerAttribute) {
if (componentsPerAttribute === 1) {
return '.x';
} else if (componentsPerAttribute === 2) {
return '.xy';
} else if (componentsPerAttribute === 3) {
return '.xyz';
}
return '';
}
function getGlslAttributeFunction(batchTable, attributeIndex) {
var attributes = batchTable._attributes;
var attribute = attributes[attributeIndex];
var componentsPerAttribute = attribute.componentsPerAttribute;
var functionName = attribute.functionName;
var functionReturnType = getComponentType(componentsPerAttribute);
var functionReturnValue = getComponentSwizzle(componentsPerAttribute);
var offset = batchTable._offsets[attributeIndex];
var glslFunction =
functionReturnType + ' ' + functionName + '(float batchId) \n' +
'{ \n' +
' vec2 st = computeSt(batchId); \n' +
' st.x += batchTextureStep.x * float(' + offset + '); \n';
if (batchTable._packFloats && attribute.componentDatatype !== PixelDatatype.UNSIGNED_BYTE) {
glslFunction += 'vec4 textureValue; \n' +
'textureValue.x = unpackFloat(texture2D(batchTexture, st)); \n' +
'textureValue.y = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x, 0.0))); \n' +
'textureValue.z = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x * 2.0, 0.0))); \n' +
'textureValue.w = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x * 3.0, 0.0))); \n';
} else {
glslFunction += ' vec4 textureValue = texture2D(batchTexture, st); \n';
}
glslFunction += ' ' + functionReturnType + ' value = textureValue' + functionReturnValue + '; \n';
if (batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && !attribute.normalize) {
glslFunction += 'value *= 255.0; \n';
} else if (batchTable._pixelDatatype === PixelDatatype.FLOAT && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && attribute.normalize) {
glslFunction += 'value /= 255.0; \n';
}
glslFunction +=
' return value; \n' +
'} \n';
return glslFunction;
}
/**
* Gets a function that will update a vertex shader to contain functions for looking up values in the batch table.
*
* @returns {BatchTable~updateVertexShaderSourceCallback} A callback for updating a vertex shader source.
*/
BatchTable.prototype.getVertexShaderCallback = function() {
var attributes = this._attributes;
if (attributes.length === 0) {
return function(source) {
return source;
};
}
var batchTableShader = 'uniform sampler2D batchTexture; \n';
batchTableShader += getGlslComputeSt(this) + '\n';
batchTableShader += getGlslUnpackFloat(this) + '\n';
var length = attributes.length;
for (var i = 0; i < length; ++i) {
batchTableShader += getGlslAttributeFunction(this, i);
}
return function(source) {
var mainIndex = source.indexOf('void main');
var beforeMain = source.substring(0, mainIndex);
var afterMain = source.substring(mainIndex);
return beforeMain + '\n' + batchTableShader + '\n' + afterMain;
};
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see BatchTable#destroy
*/
BatchTable.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see BatchTable#isDestroyed
*/
BatchTable.prototype.destroy = function() {
this._texture = this._texture && this._texture.destroy();
return destroyObject(this);
};
/**
* A callback for updating uniform maps.
* @callback BatchTable~updateUniformMapCallback
*
* @param {Object} uniformMap The uniform map.
* @returns {Object} The new uniform map with properties for retrieving values from the batch table.
*/
/**
* A callback for updating a vertex shader source.
* @callback BatchTable~updateVertexShaderSourceCallback
*
* @param {String} vertexShaderSource The vertex shader source.
* @returns {String} The new vertex shader source with the functions for retrieving batch table values injected.
*/
return BatchTable;
});
/*global define*/
define('Scene/PrimitivePipeline',[
'../Core/BoundingSphere',
'../Core/ComponentDatatype',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/FeatureDetection',
'../Core/GeographicProjection',
'../Core/Geometry',
'../Core/GeometryAttribute',
'../Core/GeometryAttributes',
'../Core/GeometryPipeline',
'../Core/IndexDatatype',
'../Core/Matrix4',
'../Core/WebMercatorProjection'
], function(
BoundingSphere,
ComponentDatatype,
defined,
DeveloperError,
Ellipsoid,
FeatureDetection,
GeographicProjection,
Geometry,
GeometryAttribute,
GeometryAttributes,
GeometryPipeline,
IndexDatatype,
Matrix4,
WebMercatorProjection) {
'use strict';
// Bail out if the browser doesn't support typed arrays, to prevent the setup function
// from failing, since we won't be able to create a WebGL context anyway.
if (!FeatureDetection.supportsTypedArrays()) {
return {};
}
function transformToWorldCoordinates(instances, primitiveModelMatrix, scene3DOnly) {
var toWorld = !scene3DOnly;
var length = instances.length;
var i;
if (!toWorld && (length > 1)) {
var modelMatrix = instances[0].modelMatrix;
for (i = 1; i < length; ++i) {
if (!Matrix4.equals(modelMatrix, instances[i].modelMatrix)) {
toWorld = true;
break;
}
}
}
if (toWorld) {
for (i = 0; i < length; ++i) {
if (defined(instances[i].geometry)) {
GeometryPipeline.transformToWorldCoordinates(instances[i]);
}
}
} else {
// Leave geometry in local coordinate system; auto update model-matrix.
Matrix4.multiplyTransformation(primitiveModelMatrix, instances[0].modelMatrix, primitiveModelMatrix);
}
}
function addGeometryBatchId(geometry, batchId) {
var attributes = geometry.attributes;
var positionAttr = attributes.position;
var numberOfComponents = positionAttr.values.length / positionAttr.componentsPerAttribute;
attributes.batchId = new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 1,
values : new Float32Array(numberOfComponents)
});
var values = attributes.batchId.values;
for (var j = 0; j < numberOfComponents; ++j) {
values[j] = batchId;
}
}
function addBatchIds(instances) {
var length = instances.length;
for (var i = 0; i < length; ++i) {
var instance = instances[i];
if (defined(instance.geometry)) {
addGeometryBatchId(instance.geometry, i);
} else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) {
addGeometryBatchId(instance.westHemisphereGeometry, i);
addGeometryBatchId(instance.eastHemisphereGeometry, i);
}
}
}
function geometryPipeline(parameters) {
var instances = parameters.instances;
var projection = parameters.projection;
var uintIndexSupport = parameters.elementIndexUintSupported;
var scene3DOnly = parameters.scene3DOnly;
var vertexCacheOptimize = parameters.vertexCacheOptimize;
var compressVertices = parameters.compressVertices;
var modelMatrix = parameters.modelMatrix;
var i;
var geometry;
var primitiveType;
var length = instances.length;
for (i = 0 ; i < length; ++i) {
if (defined(instances[i].geometry)) {
primitiveType = instances[i].geometry.primitiveType;
break;
}
}
for (i = 1; i < length; ++i) {
if (defined(instances[i].geometry) && instances[i].geometry.primitiveType !== primitiveType) {
throw new DeveloperError('All instance geometries must have the same primitiveType.');
}
}
// Unify to world coordinates before combining.
transformToWorldCoordinates(instances, modelMatrix, scene3DOnly);
// Clip to IDL
if (!scene3DOnly) {
for (i = 0; i < length; ++i) {
if (defined(instances[i].geometry)) {
GeometryPipeline.splitLongitude(instances[i]);
}
}
}
addBatchIds(instances);
// Optimize for vertex shader caches
if (vertexCacheOptimize) {
for (i = 0; i < length; ++i) {
var instance = instances[i];
if (defined(instance.geometry)) {
GeometryPipeline.reorderForPostVertexCache(instance.geometry);
GeometryPipeline.reorderForPreVertexCache(instance.geometry);
} else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) {
GeometryPipeline.reorderForPostVertexCache(instance.westHemisphereGeometry);
GeometryPipeline.reorderForPreVertexCache(instance.westHemisphereGeometry);
GeometryPipeline.reorderForPostVertexCache(instance.eastHemisphereGeometry);
GeometryPipeline.reorderForPreVertexCache(instance.eastHemisphereGeometry);
}
}
}
// Combine into single geometry for better rendering performance.
var geometries = GeometryPipeline.combineInstances(instances);
length = geometries.length;
for (i = 0; i < length; ++i) {
geometry = geometries[i];
// Split positions for GPU RTE
var attributes = geometry.attributes;
var name;
if (!scene3DOnly) {
for (name in attributes) {
if (attributes.hasOwnProperty(name) && attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
var name3D = name + '3D';
var name2D = name + '2D';
// Compute 2D positions
GeometryPipeline.projectTo2D(geometry, name, name3D, name2D, projection);
if (defined(geometry.boundingSphere) && name === 'position') {
geometry.boundingSphereCV = BoundingSphere.fromVertices(geometry.attributes.position2D.values);
}
GeometryPipeline.encodeAttribute(geometry, name3D, name3D + 'High', name3D + 'Low');
GeometryPipeline.encodeAttribute(geometry, name2D, name2D + 'High', name2D + 'Low');
}
}
} else {
for (name in attributes) {
if (attributes.hasOwnProperty(name) && attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
GeometryPipeline.encodeAttribute(geometry, name, name + '3DHigh', name + '3DLow');
}
}
}
// oct encode and pack normals, compress texture coordinates
if (compressVertices) {
GeometryPipeline.compressVertices(geometry);
}
}
if (!uintIndexSupport) {
// Break into multiple geometries to fit within unsigned short indices if needed
var splitGeometries = [];
length = geometries.length;
for (i = 0; i < length; ++i) {
geometry = geometries[i];
splitGeometries = splitGeometries.concat(GeometryPipeline.fitToUnsignedShortIndices(geometry));
}
geometries = splitGeometries;
}
return geometries;
}
function createPickOffsets(instances, geometryName, geometries, pickOffsets) {
var offset;
var indexCount;
var geometryIndex;
var offsetIndex = pickOffsets.length - 1;
if (offsetIndex >= 0) {
var pickOffset = pickOffsets[offsetIndex];
offset = pickOffset.offset + pickOffset.count;
geometryIndex = pickOffset.index;
indexCount = geometries[geometryIndex].indices.length;
} else {
offset = 0;
geometryIndex = 0;
indexCount = geometries[geometryIndex].indices.length;
}
var length = instances.length;
for (var i = 0; i < length; ++i) {
var instance = instances[i];
var geometry = instance[geometryName];
if (!defined(geometry)) {
continue;
}
var count = geometry.indices.length;
if (offset + count > indexCount) {
offset = 0;
indexCount = geometries[++geometryIndex].indices.length;
}
pickOffsets.push({
index : geometryIndex,
offset : offset,
count : count
});
offset += count;
}
}
function createInstancePickOffsets(instances, geometries) {
var pickOffsets = [];
createPickOffsets(instances, 'geometry', geometries, pickOffsets);
createPickOffsets(instances, 'westHemisphereGeometry', geometries, pickOffsets);
createPickOffsets(instances, 'eastHemisphereGeometry', geometries, pickOffsets);
return pickOffsets;
}
/**
* @private
*/
var PrimitivePipeline = {};
/**
* @private
*/
PrimitivePipeline.combineGeometry = function(parameters) {
var geometries;
var attributeLocations;
var instances = parameters.instances;
var length = instances.length;
if (length > 0) {
geometries = geometryPipeline(parameters);
if (geometries.length > 0) {
attributeLocations = GeometryPipeline.createAttributeLocations(geometries[0]);
}
}
var pickOffsets;
if (parameters.createPickOffsets && geometries.length > 0) {
pickOffsets = createInstancePickOffsets(instances, geometries);
}
var boundingSpheres = new Array(length);
var boundingSpheresCV = new Array(length);
for (var i = 0; i < length; ++i) {
var instance = instances[i];
var geometry = instance.geometry;
if (defined(geometry)) {
boundingSpheres[i] = geometry.boundingSphere;
boundingSpheresCV[i] = geometry.boundingSphereCV;
}
var eastHemisphereGeometry = instance.eastHemisphereGeometry;
var westHemisphereGeometry = instance.westHemisphereGeometry;
if (defined(eastHemisphereGeometry) && defined(westHemisphereGeometry)) {
if (defined(eastHemisphereGeometry.boundingSphere) && defined(westHemisphereGeometry.boundingSphere)) {
boundingSpheres[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphere, westHemisphereGeometry.boundingSphere);
}
if (defined(eastHemisphereGeometry.boundingSphereCV) && defined(westHemisphereGeometry.boundingSphereCV)) {
boundingSpheresCV[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphereCV, westHemisphereGeometry.boundingSphereCV);
}
}
}
return {
geometries : geometries,
modelMatrix : parameters.modelMatrix,
attributeLocations : attributeLocations,
pickOffsets : pickOffsets,
boundingSpheres : boundingSpheres,
boundingSpheresCV : boundingSpheresCV
};
};
function transferGeometry(geometry, transferableObjects) {
var attributes = geometry.attributes;
for ( var name in attributes) {
if (attributes.hasOwnProperty(name)) {
var attribute = attributes[name];
if (defined(attribute) && defined(attribute.values)) {
transferableObjects.push(attribute.values.buffer);
}
}
}
if (defined(geometry.indices)) {
transferableObjects.push(geometry.indices.buffer);
}
}
function transferGeometries(geometries, transferableObjects) {
var length = geometries.length;
for (var i = 0; i < length; ++i) {
transferGeometry(geometries[i], transferableObjects);
}
}
// This function was created by simplifying packCreateGeometryResults into a count-only operation.
function countCreateGeometryResults(items) {
var count = 1;
var length = items.length;
for (var i = 0; i < length; i++) {
var geometry = items[i];
++count;
if (!defined(geometry)) {
continue;
}
var attributes = geometry.attributes;
count += 6 + 2 * BoundingSphere.packedLength + (defined(geometry.indices) ? geometry.indices.length : 0);
for ( var property in attributes) {
if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
var attribute = attributes[property];
count += 5 + attribute.values.length;
}
}
}
return count;
}
/**
* @private
*/
PrimitivePipeline.packCreateGeometryResults = function(items, transferableObjects) {
var packedData = new Float64Array(countCreateGeometryResults(items));
var stringTable = [];
var stringHash = {};
var length = items.length;
var count = 0;
packedData[count++] = length;
for (var i = 0; i < length; i++) {
var geometry = items[i];
var validGeometry = defined(geometry);
packedData[count++] = validGeometry ? 1.0 : 0.0;
if (!validGeometry) {
continue;
}
packedData[count++] = geometry.primitiveType;
packedData[count++] = geometry.geometryType;
var validBoundingSphere = defined(geometry.boundingSphere) ? 1.0 : 0.0;
packedData[count++] = validBoundingSphere;
if (validBoundingSphere) {
BoundingSphere.pack(geometry.boundingSphere, packedData, count);
}
count += BoundingSphere.packedLength;
var validBoundingSphereCV = defined(geometry.boundingSphereCV) ? 1.0 : 0.0;
packedData[count++] = validBoundingSphereCV;
if (validBoundingSphereCV) {
BoundingSphere.pack(geometry.boundingSphereCV, packedData, count);
}
count += BoundingSphere.packedLength;
var attributes = geometry.attributes;
var attributesToWrite = [];
for ( var property in attributes) {
if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
attributesToWrite.push(property);
if (!defined(stringHash[property])) {
stringHash[property] = stringTable.length;
stringTable.push(property);
}
}
}
packedData[count++] = attributesToWrite.length;
for (var q = 0; q < attributesToWrite.length; q++) {
var name = attributesToWrite[q];
var attribute = attributes[name];
packedData[count++] = stringHash[name];
packedData[count++] = attribute.componentDatatype;
packedData[count++] = attribute.componentsPerAttribute;
packedData[count++] = attribute.normalize ? 1 : 0;
packedData[count++] = attribute.values.length;
packedData.set(attribute.values, count);
count += attribute.values.length;
}
var indicesLength = defined(geometry.indices) ? geometry.indices.length : 0;
packedData[count++] = indicesLength;
if (indicesLength > 0) {
packedData.set(geometry.indices, count);
count += indicesLength;
}
}
transferableObjects.push(packedData.buffer);
return {
stringTable : stringTable,
packedData : packedData
};
};
/**
* @private
*/
PrimitivePipeline.unpackCreateGeometryResults = function(createGeometryResult) {
var stringTable = createGeometryResult.stringTable;
var packedGeometry = createGeometryResult.packedData;
var i;
var result = new Array(packedGeometry[0]);
var resultIndex = 0;
var packedGeometryIndex = 1;
while (packedGeometryIndex < packedGeometry.length) {
var valid = packedGeometry[packedGeometryIndex++] === 1.0;
if (!valid) {
result[resultIndex++] = undefined;
continue;
}
var primitiveType = packedGeometry[packedGeometryIndex++];
var geometryType = packedGeometry[packedGeometryIndex++];
var boundingSphere;
var boundingSphereCV;
var validBoundingSphere = packedGeometry[packedGeometryIndex++] === 1.0;
if (validBoundingSphere) {
boundingSphere = BoundingSphere.unpack(packedGeometry, packedGeometryIndex);
}
packedGeometryIndex += BoundingSphere.packedLength;
var validBoundingSphereCV = packedGeometry[packedGeometryIndex++] === 1.0;
if (validBoundingSphereCV) {
boundingSphereCV = BoundingSphere.unpack(packedGeometry, packedGeometryIndex);
}
packedGeometryIndex += BoundingSphere.packedLength;
var length;
var values;
var componentsPerAttribute;
var attributes = new GeometryAttributes();
var numAttributes = packedGeometry[packedGeometryIndex++];
for (i = 0; i < numAttributes; i++) {
var name = stringTable[packedGeometry[packedGeometryIndex++]];
var componentDatatype = packedGeometry[packedGeometryIndex++];
componentsPerAttribute = packedGeometry[packedGeometryIndex++];
var normalize = packedGeometry[packedGeometryIndex++] !== 0;
length = packedGeometry[packedGeometryIndex++];
values = ComponentDatatype.createTypedArray(componentDatatype, length);
for (var valuesIndex = 0; valuesIndex < length; valuesIndex++) {
values[valuesIndex] = packedGeometry[packedGeometryIndex++];
}
attributes[name] = new GeometryAttribute({
componentDatatype : componentDatatype,
componentsPerAttribute : componentsPerAttribute,
normalize : normalize,
values : values
});
}
var indices;
length = packedGeometry[packedGeometryIndex++];
if (length > 0) {
var numberOfVertices = values.length / componentsPerAttribute;
indices = IndexDatatype.createTypedArray(numberOfVertices, length);
for (i = 0; i < length; i++) {
indices[i] = packedGeometry[packedGeometryIndex++];
}
}
result[resultIndex++] = new Geometry({
primitiveType : primitiveType,
geometryType : geometryType,
boundingSphere : boundingSphere,
boundingSphereCV : boundingSphereCV,
indices : indices,
attributes : attributes
});
}
return result;
};
function packInstancesForCombine(instances, transferableObjects) {
var length = instances.length;
var packedData = new Float64Array(1 + (length * 16));
var count = 0;
packedData[count++] = length;
for (var i = 0; i < length; i++) {
var instance = instances[i];
Matrix4.pack(instance.modelMatrix, packedData, count);
count += Matrix4.packedLength;
}
transferableObjects.push(packedData.buffer);
return packedData;
}
function unpackInstancesForCombine(data) {
var packedInstances = data;
var result = new Array(packedInstances[0]);
var count = 0;
var i = 1;
while (i < packedInstances.length) {
var modelMatrix = Matrix4.unpack(packedInstances, i);
i += Matrix4.packedLength;
result[count++] = {
modelMatrix : modelMatrix
};
}
return result;
}
/**
* @private
*/
PrimitivePipeline.packCombineGeometryParameters = function(parameters, transferableObjects) {
var createGeometryResults = parameters.createGeometryResults;
var length = createGeometryResults.length;
for (var i = 0; i < length; i++) {
transferableObjects.push(createGeometryResults[i].packedData.buffer);
}
return {
createGeometryResults : parameters.createGeometryResults,
packedInstances : packInstancesForCombine(parameters.instances, transferableObjects),
ellipsoid : parameters.ellipsoid,
isGeographic : parameters.projection instanceof GeographicProjection,
elementIndexUintSupported : parameters.elementIndexUintSupported,
scene3DOnly : parameters.scene3DOnly,
vertexCacheOptimize : parameters.vertexCacheOptimize,
compressVertices : parameters.compressVertices,
modelMatrix : parameters.modelMatrix,
createPickOffsets : parameters.createPickOffsets
};
};
/**
* @private
*/
PrimitivePipeline.unpackCombineGeometryParameters = function(packedParameters) {
var instances = unpackInstancesForCombine(packedParameters.packedInstances);
var createGeometryResults = packedParameters.createGeometryResults;
var length = createGeometryResults.length;
var instanceIndex = 0;
for (var resultIndex = 0; resultIndex < length; resultIndex++) {
var geometries = PrimitivePipeline.unpackCreateGeometryResults(createGeometryResults[resultIndex]);
var geometriesLength = geometries.length;
for (var geometryIndex = 0; geometryIndex < geometriesLength; geometryIndex++) {
var geometry = geometries[geometryIndex];
var instance = instances[instanceIndex];
instance.geometry = geometry;
++instanceIndex;
}
}
var ellipsoid = Ellipsoid.clone(packedParameters.ellipsoid);
var projection = packedParameters.isGeographic ? new GeographicProjection(ellipsoid) : new WebMercatorProjection(ellipsoid);
return {
instances : instances,
ellipsoid : ellipsoid,
projection : projection,
elementIndexUintSupported : packedParameters.elementIndexUintSupported,
scene3DOnly : packedParameters.scene3DOnly,
vertexCacheOptimize : packedParameters.vertexCacheOptimize,
compressVertices : packedParameters.compressVertices,
modelMatrix : Matrix4.clone(packedParameters.modelMatrix),
createPickOffsets : packedParameters.createPickOffsets
};
};
function packBoundingSpheres(boundingSpheres) {
var length = boundingSpheres.length;
var bufferLength = 1 + (BoundingSphere.packedLength + 1) * length;
var buffer = new Float32Array(bufferLength);
var bufferIndex = 0;
buffer[bufferIndex++] = length;
for (var i = 0; i < length; ++i) {
var bs = boundingSpheres[i];
if (!defined(bs)) {
buffer[bufferIndex++] = 0.0;
} else {
buffer[bufferIndex++] = 1.0;
BoundingSphere.pack(boundingSpheres[i], buffer, bufferIndex);
}
bufferIndex += BoundingSphere.packedLength;
}
return buffer;
}
function unpackBoundingSpheres(buffer) {
var result = new Array(buffer[0]);
var count = 0;
var i = 1;
while (i < buffer.length) {
if (buffer[i++] === 1.0) {
result[count] = BoundingSphere.unpack(buffer, i);
}
++count;
i += BoundingSphere.packedLength;
}
return result;
}
/**
* @private
*/
PrimitivePipeline.packCombineGeometryResults = function(results, transferableObjects) {
if (defined(results.geometries)) {
transferGeometries(results.geometries, transferableObjects);
}
var packedBoundingSpheres = packBoundingSpheres(results.boundingSpheres);
var packedBoundingSpheresCV = packBoundingSpheres(results.boundingSpheresCV);
transferableObjects.push(packedBoundingSpheres.buffer, packedBoundingSpheresCV.buffer);
return {
geometries : results.geometries,
attributeLocations : results.attributeLocations,
modelMatrix : results.modelMatrix,
pickOffsets : results.pickOffsets,
boundingSpheres : packedBoundingSpheres,
boundingSpheresCV : packedBoundingSpheresCV
};
};
/**
* @private
*/
PrimitivePipeline.unpackCombineGeometryResults = function(packedResult) {
return {
geometries : packedResult.geometries,
attributeLocations : packedResult.attributeLocations,
modelMatrix : packedResult.modelMatrix,
pickOffsets : packedResult.pickOffsets,
boundingSpheres : unpackBoundingSpheres(packedResult.boundingSpheres),
boundingSpheresCV : unpackBoundingSpheres(packedResult.boundingSpheresCV)
};
};
return PrimitivePipeline;
});
/*global define*/
define('Scene/PrimitiveState',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* @private
*/
var PrimitiveState = {
READY : 0,
CREATING : 1,
CREATED : 2,
COMBINING : 3,
COMBINED : 4,
COMPLETE : 5,
FAILED : 6
};
return freezeObject(PrimitiveState);
});
/*global define*/
define('Scene/SceneMode',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Indicates if the scene is viewed in 3D, 2D, or 2.5D Columbus view.
*
* @exports SceneMode
*
* @see Scene#mode
*/
var SceneMode = {
/**
* Morphing between mode, e.g., 3D to 2D.
*
* @type {Number}
* @constant
*/
MORPHING : 0,
/**
* Columbus View mode. A 2.5D perspective view where the map is laid out
* flat and objects with non-zero height are drawn above it.
*
* @type {Number}
* @constant
*/
COLUMBUS_VIEW : 1,
/**
* 2D mode. The map is viewed top-down with an orthographic projection.
*
* @type {Number}
* @constant
*/
SCENE2D : 2,
/**
* 3D mode. A traditional 3D perspective view of the globe.
*
* @type {Number}
* @constant
*/
SCENE3D : 3
};
/**
* Returns the morph time for the given scene mode.
*
* @param {SceneMode} value The scene mode
* @returns {Number} The morph time
*/
SceneMode.getMorphTime = function(value) {
if (value === SceneMode.SCENE3D) {
return 1.0;
} else if (value === SceneMode.MORPHING) {
return undefined;
}
return 0.0;
};
return freezeObject(SceneMode);
});
/*global define*/
define('Scene/ShadowMode',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Specifies whether the object casts or receives shadows from each light source when
* shadows are enabled.
*
* @exports ShadowMode
*/
var ShadowMode = {
/**
* The object does not cast or receive shadows.
*
* @type {Number}
* @constant
*/
DISABLED : 0,
/**
* The object casts and receives shadows.
*
* @type {Number}
* @constant
*/
ENABLED : 1,
/**
* The object casts shadows only.
*
* @type {Number}
* @constant
*/
CAST_ONLY : 2,
/**
* The object receives shadows only.
*
* @type {Number}
* @constant
*/
RECEIVE_ONLY : 3,
/**
* @private
*/
NUMBER_OF_SHADOW_MODES : 4
};
/**
* @private
*/
ShadowMode.castShadows = function(shadowMode) {
return (shadowMode === ShadowMode.ENABLED) || (shadowMode === ShadowMode.CAST_ONLY);
};
/**
* @private
*/
ShadowMode.receiveShadows = function(shadowMode) {
return (shadowMode === ShadowMode.ENABLED) || (shadowMode === ShadowMode.RECEIVE_ONLY);
};
/**
* @private
*/
ShadowMode.fromCastReceive = function(castShadows, receiveShadows) {
if (castShadows && receiveShadows) {
return ShadowMode.ENABLED;
} else if (castShadows) {
return ShadowMode.CAST_ONLY;
} else if (receiveShadows) {
return ShadowMode.RECEIVE_ONLY;
} else {
return ShadowMode.DISABLED;
}
};
return freezeObject(ShadowMode);
});
/*global define*/
define('Scene/Primitive',[
'../Core/BoundingSphere',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/clone',
'../Core/Color',
'../Core/combine',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EncodedCartesian3',
'../Core/FeatureDetection',
'../Core/Geometry',
'../Core/GeometryAttribute',
'../Core/GeometryAttributes',
'../Core/isArray',
'../Core/Matrix4',
'../Core/RuntimeError',
'../Core/subdivideArray',
'../Core/TaskProcessor',
'../Renderer/BufferUsage',
'../Renderer/ContextLimits',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArray',
'../ThirdParty/when',
'./BatchTable',
'./CullFace',
'./PrimitivePipeline',
'./PrimitiveState',
'./SceneMode',
'./ShadowMode'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
clone,
Color,
combine,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EncodedCartesian3,
FeatureDetection,
Geometry,
GeometryAttribute,
GeometryAttributes,
isArray,
Matrix4,
RuntimeError,
subdivideArray,
TaskProcessor,
BufferUsage,
ContextLimits,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
ShaderSource,
VertexArray,
when,
BatchTable,
CullFace,
PrimitivePipeline,
PrimitiveState,
SceneMode,
ShadowMode) {
'use strict';
/**
* A primitive represents geometry in the {@link Scene}. The geometry can be from a single {@link GeometryInstance}
* as shown in example 1 below, or from an array of instances, even if the geometry is from different
* geometry types, e.g., an {@link RectangleGeometry} and an {@link EllipsoidGeometry} as shown in Code Example 2.
*
* A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including
* {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement,
* and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix
* and match most of them and add a new geometry or appearance independently of each other.
*
*
* Combining multiple instances into one primitive is called batching, and significantly improves performance for static data.
* Instances can be individually picked; {@link Scene#pick} returns their {@link GeometryInstance#id}. Using
* per-instance appearances like {@link PerInstanceColorAppearance}, each instance can also have a unique color.
*
*
* {@link Geometry} can either be created and batched on a web worker or the main thread. The first two examples
* show geometry that will be created on a web worker by using the descriptions of the geometry. The third example
* shows how to create the geometry on the main thread by explicitly calling the createGeometry
method.
*
*
* @alias Primitive
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {GeometryInstance[]|GeometryInstance} [options.geometryInstances] The geometry instances - or a single geometry instance - to render.
* @param {Appearance} [options.appearance] The appearance used to render the primitive.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates.
* @param {Boolean} [options.vertexCacheOptimize=false] When true
, geometry vertices are optimized for the pre and post-vertex-shader caches.
* @param {Boolean} [options.interleave=false] When true
, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
* @param {Boolean} [options.compressVertices=true] When true
, the geometry vertices are compressed, which will save memory.
* @param {Boolean} [options.releaseGeometryInstances=true] When true
, the primitive does not keep a reference to the input geometryInstances
to save memory.
* @param {Boolean} [options.allowPicking=true] When true
, each geometry instance will only be pickable with {@link Scene#pick}. When false
, GPU memory is saved.
* @param {Boolean} [options.cull=true] When true
, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume. Set this to false
for a small performance gain if you are manually culling the primitive.
* @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
* @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from each light source.
*
* @example
* // 1. Draw a translucent ellipse on the surface with a checkerboard pattern
* var instance = new Cesium.GeometryInstance({
* geometry : new Cesium.EllipseGeometry({
* center : Cesium.Cartesian3.fromDegrees(-100.0, 20.0),
* semiMinorAxis : 500000.0,
* semiMajorAxis : 1000000.0,
* rotation : Cesium.Math.PI_OVER_FOUR,
* vertexFormat : Cesium.VertexFormat.POSITION_AND_ST
* }),
* id : 'object returned when this instance is picked and to get/set per-instance attributes'
* });
* scene.primitives.add(new Cesium.Primitive({
* geometryInstances : instance,
* appearance : new Cesium.EllipsoidSurfaceAppearance({
* material : Cesium.Material.fromType('Checkerboard')
* })
* }));
*
* @example
* // 2. Draw different instances each with a unique color
* var rectangleInstance = new Cesium.GeometryInstance({
* geometry : new Cesium.RectangleGeometry({
* rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0),
* vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
* }),
* id : 'rectangle',
* attributes : {
* color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
* }
* });
* var ellipsoidInstance = new Cesium.GeometryInstance({
* geometry : new Cesium.EllipsoidGeometry({
* radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0),
* vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL
* }),
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()),
* id : 'ellipsoid',
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
* }
* });
* scene.primitives.add(new Cesium.Primitive({
* geometryInstances : [rectangleInstance, ellipsoidInstance],
* appearance : new Cesium.PerInstanceColorAppearance()
* }));
*
* @example
* // 3. Create the geometry on the main thread.
* scene.primitives.add(new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : Cesium.EllipsoidGeometry.createGeometry(new Cesium.EllipsoidGeometry({
* radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0),
* vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL
* })),
* modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
* Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()),
* id : 'ellipsoid',
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
* }
* }),
* appearance : new Cesium.PerInstanceColorAppearance()
* }));
*
* @see GeometryInstance
* @see Appearance
*/
function Primitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The geometry instances rendered with this primitive. This may
* be undefined
if options.releaseGeometryInstances
* is true
when the primitive is constructed.
*
* Changing this property after the primitive is rendered has no effect.
*
*
* @type GeometryInstance[]|GeometryInstance
*
* @default undefined
*/
this.geometryInstances = options.geometryInstances;
/**
* The {@link Appearance} used to shade this primitive. Each geometry
* instance is shaded with the same appearance. Some appearances, like
* {@link PerInstanceColorAppearance} allow giving each instance unique
* properties.
*
* @type Appearance
*
* @default undefined
*/
this.appearance = options.appearance;
this._appearance = undefined;
this._material = undefined;
/**
* The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates.
* When this is the identity matrix, the primitive is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
*
* This property is only supported in 3D mode.
*
*
* @type Matrix4
*
* @default Matrix4.IDENTITY
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = new Matrix4();
/**
* Determines if the primitive will be shown. This affects all geometry
* instances in the primitive.
*
* @type Boolean
*
* @default true
*/
this.show = defaultValue(options.show, true);
this._vertexCacheOptimize = defaultValue(options.vertexCacheOptimize, false);
this._interleave = defaultValue(options.interleave, false);
this._releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true);
this._allowPicking = defaultValue(options.allowPicking, true);
this._asynchronous = defaultValue(options.asynchronous, true);
this._compressVertices = defaultValue(options.compressVertices, true);
/**
* When true
, the renderer frustum culls and horizon culls the primitive's commands
* based on their bounding volume. Set this to false
for a small performance gain
* if you are manually culling the primitive.
*
* @type {Boolean}
*
* @default true
*/
this.cull = defaultValue(options.cull, true);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
/**
* @private
*/
this.rtcCenter = options.rtcCenter;
if (defined(this.rtcCenter) && (!defined(this.geometryInstances) || (isArray(this.geometryInstances) && this.geometryInstances !== 1))) {
throw new DeveloperError('Relative-to-center rendering only supports one geometry instance.');
}
/**
* Determines whether this primitive casts or receives shadows from each light source.
*
* @type {ShadowMode}
*
* @default ShadowMode.DISABLED
*/
this.shadows = defaultValue(options.shadows, ShadowMode.DISABLED);
this._translucent = undefined;
this._state = PrimitiveState.READY;
this._geometries = [];
this._error = undefined;
this._numberOfInstances = 0;
this._boundingSpheres = [];
this._boundingSphereWC = [];
this._boundingSphereCV = [];
this._boundingSphere2D = [];
this._boundingSphereMorph = [];
this._perInstanceAttributeCache = [];
this._instanceIds = [];
this._lastPerInstanceAttributeIndex = 0;
this._va = [];
this._attributeLocations = undefined;
this._primitiveType = undefined;
this._frontFaceRS = undefined;
this._backFaceRS = undefined;
this._sp = undefined;
this._pickRS = undefined;
this._pickSP = undefined;
this._pickIds = [];
this._colorCommands = [];
this._pickCommands = [];
this._readOnlyInstanceAttributes = options._readOnlyInstanceAttributes;
this._createBoundingVolumeFunction = options._createBoundingVolumeFunction;
this._createRenderStatesFunction = options._createRenderStatesFunction;
this._createShaderProgramFunction = options._createShaderProgramFunction;
this._createCommandsFunction = options._createCommandsFunction;
this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction;
this._createPickOffsets = options._createPickOffsets;
this._pickOffsets = undefined;
this._createGeometryResults = undefined;
this._ready = false;
this._readyPromise = when.defer();
this._batchTable = undefined;
this._batchTableAttributeIndices = undefined;
this._instanceBoundingSpheres = undefined;
this._instanceBoundingSpheresCV = undefined;
this._batchTableBoundingSpheresUpdated = false;
this._batchTableBoundingSphereAttributeIndices = undefined;
}
defineProperties(Primitive.prototype, {
/**
* When true
, geometry vertices are optimized for the pre and post-vertex-shader caches.
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
vertexCacheOptimize : {
get : function() {
return this._vertexCacheOptimize;
}
},
/**
* Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
interleave : {
get : function() {
return this._interleave;
}
},
/**
* When true
, the primitive does not keep a reference to the input geometryInstances
to save memory.
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
releaseGeometryInstances : {
get : function() {
return this._releaseGeometryInstances;
}
},
/**
* When true
, each geometry instance will only be pickable with {@link Scene#pick}. When false
, GPU memory is saved. *
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
allowPicking : {
get : function() {
return this._allowPicking;
}
},
/**
* Determines if the geometry instances will be created and batched on a web worker.
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
asynchronous : {
get : function() {
return this._asynchronous;
}
},
/**
* When true
, geometry vertices are compressed, which will save memory.
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
compressVertices : {
get : function() {
return this._compressVertices;
}
},
/**
* Determines if the primitive is complete and ready to render. If this property is
* true, the primitive will be rendered the next time that {@link Primitive#update}
* is called.
*
* @memberof Primitive.prototype
*
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves when the primitive is ready to render.
* @memberof Primitive.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
}
});
function getCommonPerInstanceAttributeNames(instances) {
var length = instances.length;
var attributesInAllInstances = [];
var attributes0 = instances[0].attributes;
var name;
for (name in attributes0) {
if (attributes0.hasOwnProperty(name)) {
var attribute = attributes0[name];
var inAllInstances = true;
// Does this same attribute exist in all instances?
for (var i = 1; i < length; ++i) {
var otherAttribute = instances[i].attributes[name];
if (!defined(otherAttribute) ||
(attribute.componentDatatype !== otherAttribute.componentDatatype) ||
(attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) ||
(attribute.normalize !== otherAttribute.normalize)) {
inAllInstances = false;
break;
}
}
if (inAllInstances) {
attributesInAllInstances.push(name);
}
}
}
return attributesInAllInstances;
}
var scratchGetAttributeCartesian2 = new Cartesian2();
var scratchGetAttributeCartesian3 = new Cartesian3();
var scratchGetAttributeCartesian4 = new Cartesian4();
function getAttributeValue(value) {
var componentsPerAttribute = value.length;
if (componentsPerAttribute === 1) {
return value[0];
} else if (componentsPerAttribute === 2) {
return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2);
} else if (componentsPerAttribute === 3) {
return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3);
} else if (componentsPerAttribute === 4) {
return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4);
}
}
function createBatchTable(primitive, context) {
var geometryInstances = primitive.geometryInstances;
var instances = (isArray(geometryInstances)) ? geometryInstances : [geometryInstances];
var numberOfInstances = instances.length;
if (numberOfInstances === 0) {
return;
}
var names = getCommonPerInstanceAttributeNames(instances);
var length = names.length;
var allowPicking = primitive.allowPicking;
var attributes = [];
var attributeIndices = {};
var boundingSphereAttributeIndices = {};
var firstInstance = instances[0];
var instanceAttributes = firstInstance.attributes;
var i;
var name;
var attribute;
for (i = 0; i < length; ++i) {
name = names[i];
attribute = instanceAttributes[name];
attributeIndices[name] = i;
attributes.push({
functionName : 'czm_batchTable_' + name,
componentDatatype : attribute.componentDatatype,
componentsPerAttribute : attribute.componentsPerAttribute,
normalize : attribute.normalize
});
}
if (names.indexOf('distanceDisplayCondition') !== -1) {
attributes.push({
functionName : 'czm_batchTable_boundingSphereCenter3DHigh',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3
},{
functionName : 'czm_batchTable_boundingSphereCenter3DLow',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3
},{
functionName : 'czm_batchTable_boundingSphereCenter2DHigh',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3
},{
functionName : 'czm_batchTable_boundingSphereCenter2DLow',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3
},{
functionName : 'czm_batchTable_boundingSphereRadius',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 1
});
boundingSphereAttributeIndices.center3DHigh = attributes.length - 5;
boundingSphereAttributeIndices.center3DLow = attributes.length - 4;
boundingSphereAttributeIndices.center2DHigh = attributes.length - 3;
boundingSphereAttributeIndices.center2DLow = attributes.length - 2;
boundingSphereAttributeIndices.radius = attributes.length - 1;
}
if (allowPicking) {
attributes.push({
functionName : 'czm_batchTable_pickColor',
componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
componentsPerAttribute : 4,
normalize : true
});
}
var attributesLength = attributes.length;
var batchTable = new BatchTable(context, attributes, numberOfInstances);
for (i = 0; i < numberOfInstances; ++i) {
var instance = instances[i];
instanceAttributes = instance.attributes;
for (var j = 0; j < length; ++j) {
name = names[j];
attribute = instanceAttributes[name];
var value = getAttributeValue(attribute.value);
var attributeIndex = attributeIndices[name];
batchTable.setBatchedAttribute(i, attributeIndex, value);
}
if (allowPicking) {
var pickObject = {
primitive : defaultValue(instance.pickPrimitive, primitive)
};
if (defined(instance.id)) {
pickObject.id = instance.id;
}
var pickId = context.createPickId(pickObject);
primitive._pickIds.push(pickId);
var pickColor = pickId.color;
var color = scratchGetAttributeCartesian4;
color.x = Color.floatToByte(pickColor.red);
color.y = Color.floatToByte(pickColor.green);
color.z = Color.floatToByte(pickColor.blue);
color.w = Color.floatToByte(pickColor.alpha);
batchTable.setBatchedAttribute(i, attributesLength - 1, color);
}
}
primitive._batchTable = batchTable;
primitive._batchTableAttributeIndices = attributeIndices;
primitive._batchTableBoundingSphereAttributeIndices = boundingSphereAttributeIndices;
}
function cloneAttribute(attribute) {
var clonedValues;
if (isArray(attribute.values)) {
clonedValues = attribute.values.slice(0);
} else {
clonedValues = new attribute.values.constructor(attribute.values);
}
return new GeometryAttribute({
componentDatatype : attribute.componentDatatype,
componentsPerAttribute : attribute.componentsPerAttribute,
normalize : attribute.normalize,
values : clonedValues
});
}
function cloneGeometry(geometry) {
var attributes = geometry.attributes;
var newAttributes = new GeometryAttributes();
for (var property in attributes) {
if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
newAttributes[property] = cloneAttribute(attributes[property]);
}
}
var indices;
if (defined(geometry.indices)) {
var sourceValues = geometry.indices;
if (isArray(sourceValues)) {
indices = sourceValues.slice(0);
} else {
indices = new sourceValues.constructor(sourceValues);
}
}
return new Geometry({
attributes : newAttributes,
indices : indices,
primitiveType : geometry.primitiveType,
boundingSphere : BoundingSphere.clone(geometry.boundingSphere)
});
}
function cloneInstance(instance, geometry) {
return {
geometry : geometry,
modelMatrix : Matrix4.clone(instance.modelMatrix),
pickPrimitive : instance.pickPrimitive,
id : instance.id
};
}
var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g;
Primitive._modifyShaderPosition = function(primitive, vertexShaderSource, scene3DOnly) {
var match;
var forwardDecl = '';
var attributes = '';
var computeFunctions = '';
while ((match = positionRegex.exec(vertexShaderSource)) !== null) {
var name = match[1];
var functionName = 'vec4 czm_compute' + name[0].toUpperCase() + name.substr(1) + '()';
// Don't forward-declare czm_computePosition because computePosition.glsl already does.
if (functionName !== 'vec4 czm_computePosition()') {
forwardDecl += functionName + ';\n';
}
if (!defined(primitive.rtcCenter)) {
// Use GPU RTE
if (!scene3DOnly) {
attributes +=
'attribute vec3 ' + name + '2DHigh;\n' +
'attribute vec3 ' + name + '2DLow;\n';
computeFunctions +=
functionName + '\n' +
'{\n' +
' vec4 p;\n' +
' if (czm_morphTime == 1.0)\n' +
' {\n' +
' p = czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' +
' }\n' +
' else if (czm_morphTime == 0.0)\n' +
' {\n' +
' p = czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy);\n' +
' }\n' +
' else\n' +
' {\n' +
' p = czm_columbusViewMorph(\n' +
' czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy),\n' +
' czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow),\n' +
' czm_morphTime);\n' +
' }\n' +
' return p;\n' +
'}\n\n';
} else {
computeFunctions +=
functionName + '\n' +
'{\n' +
' return czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' +
'}\n\n';
}
} else {
// Use RTC
vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DHigh;/g, '');
vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DLow;/g, '');
forwardDecl += 'uniform mat4 u_modifiedModelView;\n';
attributes += 'attribute vec4 position;\n';
computeFunctions +=
functionName + '\n' +
'{\n' +
' return u_modifiedModelView * position;\n' +
'}\n\n';
vertexShaderSource = vertexShaderSource.replace(/czm_modelViewRelativeToEye\s+\*\s+/g, '');
vertexShaderSource = vertexShaderSource.replace(/czm_modelViewProjectionRelativeToEye/g, 'czm_projection');
}
}
return [forwardDecl, attributes, vertexShaderSource, computeFunctions].join('\n');
};
Primitive._appendShowToShader = function(primitive, vertexShaderSource) {
if (!defined(primitive._batchTableAttributeIndices.show)) {
return vertexShaderSource;
}
var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_show_main');
var showMain =
'void main() \n' +
'{ \n' +
' czm_non_show_main(); \n' +
' gl_Position *= czm_batchTable_show(batchId); \n' +
'}';
return renamedVS + '\n' + showMain;
};
Primitive._updateColorAttribute = function(primitive, vertexShaderSource) {
// some appearances have a color attribute for per vertex color.
// only remove if color is a per instance attribute.
if (!defined(primitive._batchTableAttributeIndices.color)) {
return vertexShaderSource;
}
if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) {
return vertexShaderSource;
}
var modifiedVS = vertexShaderSource;
modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, '');
modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2');
return modifiedVS;
};
Primitive._updatePickColorAttribute = function(source) {
var vsPick = source.replace(/attribute\s+vec4\s+pickColor;/g, '');
vsPick = vsPick.replace(/(\b)pickColor(\b)/g, '$1czm_batchTable_pickColor(batchId)$2');
return vsPick;
};
Primitive._appendDistanceDisplayConditionToShader = function(primitive, vertexShaderSource, scene3DOnly) {
if (!defined(primitive._batchTableAttributeIndices.distanceDisplayCondition)) {
return vertexShaderSource;
}
var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_distanceDisplayCondition_main');
var distanceDisplayConditionMain =
'void main() \n' +
'{ \n' +
' czm_non_distanceDisplayCondition_main(); \n' +
' vec2 distanceDisplayCondition = czm_batchTable_distanceDisplayCondition(batchId);\n' +
' vec3 boundingSphereCenter3DHigh = czm_batchTable_boundingSphereCenter3DHigh(batchId);\n' +
' vec3 boundingSphereCenter3DLow = czm_batchTable_boundingSphereCenter3DLow(batchId);\n' +
' vec3 boundingSphereCenter2DHigh = czm_batchTable_boundingSphereCenter2DHigh(batchId);\n' +
' vec3 boundingSphereCenter2DLow = czm_batchTable_boundingSphereCenter2DLow(batchId);\n' +
' float boundingSphereRadius = czm_batchTable_boundingSphereRadius(batchId);\n';
if (!scene3DOnly) {
distanceDisplayConditionMain +=
' vec4 centerRTE;\n' +
' if (czm_morphTime == 1.0)\n' +
' {\n' +
' centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n' +
' }\n' +
' else if (czm_morphTime == 0.0)\n' +
' {\n' +
' centerRTE = czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy);\n' +
' }\n' +
' else\n' +
' {\n' +
' centerRTE = czm_columbusViewMorph(\n' +
' czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy),\n' +
' czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow),\n' +
' czm_morphTime);\n' +
' }\n';
} else {
distanceDisplayConditionMain +=
' vec4 centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n';
}
distanceDisplayConditionMain +=
' float radiusSq = boundingSphereRadius * boundingSphereRadius; \n' +
' float distanceSq; \n' +
' if (czm_sceneMode == czm_sceneMode2D) \n' +
' { \n' +
' distanceSq = czm_eyeHeight2D.y - radiusSq; \n' +
' } \n' +
' else \n' +
' { \n' +
' distanceSq = dot(centerRTE.xyz, centerRTE.xyz) - radiusSq; \n' +
' } \n' +
' distanceSq = max(distanceSq, 0.0); \n' +
' float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; \n' +
' float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; \n' +
' float show = (distanceSq >= nearSq && distanceSq <= farSq) ? 1.0 : 0.0; \n' +
' gl_Position *= show; \n' +
'}';
return renamedVS + '\n' + distanceDisplayConditionMain;
};
function modifyForEncodedNormals(primitive, vertexShaderSource) {
if (!primitive.compressVertices) {
return vertexShaderSource;
}
var containsNormal = vertexShaderSource.search(/attribute\s+vec3\s+normal;/g) !== -1;
var containsSt = vertexShaderSource.search(/attribute\s+vec2\s+st;/g) !== -1;
if (!containsNormal && !containsSt) {
return vertexShaderSource;
}
var containsTangent = vertexShaderSource.search(/attribute\s+vec3\s+tangent;/g) !== -1;
var containsBinormal = vertexShaderSource.search(/attribute\s+vec3\s+binormal;/g) !== -1;
var numComponents = containsSt && containsNormal ? 2.0 : 1.0;
numComponents += containsTangent || containsBinormal ? 1 : 0;
var type = (numComponents > 1) ? 'vec' + numComponents : 'float';
var attributeName = 'compressedAttributes';
var attributeDecl = 'attribute ' + type + ' ' + attributeName + ';';
var globalDecl = '';
var decode = '';
if (containsSt) {
globalDecl += 'vec2 st;\n';
var stComponent = numComponents > 1 ? attributeName + '.x' : attributeName;
decode += ' st = czm_decompressTextureCoordinates(' + stComponent + ');\n';
}
if (containsNormal && containsTangent && containsBinormal) {
globalDecl +=
'vec3 normal;\n' +
'vec3 tangent;\n' +
'vec3 binormal;\n';
decode += ' czm_octDecode(' + attributeName + '.' + (containsSt ? 'yz' : 'xy') + ', normal, tangent, binormal);\n';
} else {
if (containsNormal) {
globalDecl += 'vec3 normal;\n';
decode += ' normal = czm_octDecode(' + attributeName + (numComponents > 1 ? '.' + (containsSt ? 'y' : 'x') : '') + ');\n';
}
if (containsTangent) {
globalDecl += 'vec3 tangent;\n';
decode += ' tangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n';
}
if (containsBinormal) {
globalDecl += 'vec3 binormal;\n';
decode += ' binormal = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n';
}
}
var modifiedVS = vertexShaderSource;
modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, '');
modifiedVS = modifiedVS.replace(/attribute\s+vec2\s+st;/g, '');
modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+tangent;/g, '');
modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+binormal;/g, '');
modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main');
var compressedMain =
'void main() \n' +
'{ \n' +
decode +
' czm_non_compressed_main(); \n' +
'}';
return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n');
}
function validateShaderMatching(shaderProgram, attributeLocations) {
// For a VAO and shader program to be compatible, the VAO must have
// all active attribute in the shader program. The VAO may have
// extra attributes with the only concern being a potential
// performance hit due to extra memory bandwidth and cache pollution.
// The shader source could have extra attributes that are not used,
// but there is no guarantee they will be optimized out.
//
// Here, we validate that the VAO has all attributes required
// to match the shader program.
var shaderAttributes = shaderProgram.vertexAttributes;
for (var name in shaderAttributes) {
if (shaderAttributes.hasOwnProperty(name)) {
if (!defined(attributeLocations[name])) {
throw new DeveloperError('Appearance/Geometry mismatch. The appearance requires vertex shader attribute input \'' + name +
'\', which was not computed as part of the Geometry. Use the appearance\'s vertexFormat property when constructing the geometry.');
}
}
}
}
function getUniformFunction(uniforms, name) {
return function() {
return uniforms[name];
};
}
var numberOfCreationWorkers = Math.max(FeatureDetection.hardwareConcurrency - 1, 1);
var createGeometryTaskProcessors;
var combineGeometryTaskProcessor = new TaskProcessor('combineGeometry', Number.POSITIVE_INFINITY);
function loadAsynchronous(primitive, frameState) {
var instances;
var geometry;
var i;
var j;
var instanceIds = primitive._instanceIds;
if (primitive._state === PrimitiveState.READY) {
instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
var length = primitive._numberOfInstances = instances.length;
var promises = [];
var subTasks = [];
for (i = 0; i < length; ++i) {
geometry = instances[i].geometry;
instanceIds.push(instances[i].id);
if (!defined(geometry._workerName)) {
throw new DeveloperError('_workerName must be defined for asynchronous geometry.');
}
subTasks.push({
moduleName : geometry._workerName,
geometry : geometry
});
}
if (!defined(createGeometryTaskProcessors)) {
createGeometryTaskProcessors = new Array(numberOfCreationWorkers);
for (i = 0; i < numberOfCreationWorkers; i++) {
createGeometryTaskProcessors[i] = new TaskProcessor('createGeometry', Number.POSITIVE_INFINITY);
}
}
var subTask;
subTasks = subdivideArray(subTasks, numberOfCreationWorkers);
for (i = 0; i < subTasks.length; i++) {
var packedLength = 0;
var workerSubTasks = subTasks[i];
var workerSubTasksLength = workerSubTasks.length;
for (j = 0; j < workerSubTasksLength; ++j) {
subTask = workerSubTasks[j];
geometry = subTask.geometry;
if (defined(geometry.constructor.pack)) {
subTask.offset = packedLength;
packedLength += defaultValue(geometry.constructor.packedLength, geometry.packedLength);
}
}
var subTaskTransferableObjects;
if (packedLength > 0) {
var array = new Float64Array(packedLength);
subTaskTransferableObjects = [array.buffer];
for (j = 0; j < workerSubTasksLength; ++j) {
subTask = workerSubTasks[j];
geometry = subTask.geometry;
if (defined(geometry.constructor.pack)) {
geometry.constructor.pack(geometry, array, subTask.offset);
subTask.geometry = array;
}
}
}
promises.push(createGeometryTaskProcessors[i].scheduleTask({
subTasks : subTasks[i]
}, subTaskTransferableObjects));
}
primitive._state = PrimitiveState.CREATING;
when.all(promises, function(results) {
primitive._createGeometryResults = results;
primitive._state = PrimitiveState.CREATED;
}).otherwise(function(error) {
setReady(primitive, frameState, PrimitiveState.FAILED, error);
});
} else if (primitive._state === PrimitiveState.CREATED) {
var transferableObjects = [];
instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
var scene3DOnly = frameState.scene3DOnly;
var projection = frameState.mapProjection;
var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({
createGeometryResults : primitive._createGeometryResults,
instances : instances,
ellipsoid : projection.ellipsoid,
projection : projection,
elementIndexUintSupported : frameState.context.elementIndexUint,
scene3DOnly : scene3DOnly,
vertexCacheOptimize : primitive.vertexCacheOptimize,
compressVertices : primitive.compressVertices,
modelMatrix : primitive.modelMatrix,
createPickOffsets : primitive._createPickOffsets
}, transferableObjects), transferableObjects);
primitive._createGeometryResults = undefined;
primitive._state = PrimitiveState.COMBINING;
when(promise, function(packedResult) {
var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult);
primitive._geometries = result.geometries;
primitive._attributeLocations = result.attributeLocations;
primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
primitive._pickOffsets = result.pickOffsets;
primitive._instanceBoundingSpheres = result.boundingSpheres;
primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;
if (defined(primitive._geometries) && primitive._geometries.length > 0) {
primitive._state = PrimitiveState.COMBINED;
} else {
setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
}
}).otherwise(function(error) {
setReady(primitive, frameState, PrimitiveState.FAILED, error);
});
}
}
function loadSynchronous(primitive, frameState) {
var instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
var length = primitive._numberOfInstances = instances.length;
var clonedInstances = new Array(length);
var instanceIds = primitive._instanceIds;
var instance;
var i;
var geometryIndex = 0;
for (i = 0; i < length; i++) {
instance = instances[i];
var geometry = instance.geometry;
var createdGeometry;
if (defined(geometry.attributes) && defined(geometry.primitiveType)) {
createdGeometry = cloneGeometry(geometry);
} else {
createdGeometry = geometry.constructor.createGeometry(geometry);
}
clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry);
instanceIds.push(instance.id);
}
clonedInstances.length = geometryIndex;
var scene3DOnly = frameState.scene3DOnly;
var projection = frameState.mapProjection;
var result = PrimitivePipeline.combineGeometry({
instances : clonedInstances,
ellipsoid : projection.ellipsoid,
projection : projection,
elementIndexUintSupported : frameState.context.elementIndexUint,
scene3DOnly : scene3DOnly,
vertexCacheOptimize : primitive.vertexCacheOptimize,
compressVertices : primitive.compressVertices,
modelMatrix : primitive.modelMatrix,
createPickOffsets : primitive._createPickOffsets
});
primitive._geometries = result.geometries;
primitive._attributeLocations = result.attributeLocations;
primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
primitive._pickOffsets = result.pickOffsets;
primitive._instanceBoundingSpheres = result.boundingSpheres;
primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;
if (defined(primitive._geometries) && primitive._geometries.length > 0) {
primitive._state = PrimitiveState.COMBINED;
} else {
setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
}
}
var scratchBoundingSphereCenterEncoded = new EncodedCartesian3();
var scratchBoundingSphereCartographic = new Cartographic();
var scratchBoundingSphereCenter2D = new Cartesian3();
function updateBatchTableBoundingSpheres(primitive, frameState) {
var hasDistanceDisplayCondition = defined(primitive._batchTableAttributeIndices.distanceDisplayCondition);
if (!hasDistanceDisplayCondition && primitive._batchTableBoundingSpheresUpdated) {
return;
}
var indices = primitive._batchTableBoundingSphereAttributeIndices;
var center3DHighIndex = indices.center3DHigh;
var center3DLowIndex = indices.center3DLow;
var center2DHighIndex = indices.center2DHigh;
var center2DLowIndex = indices.center2DLow;
var radiusIndex = indices.radius;
var projection = frameState.mapProjection;
var ellipsoid = projection.ellipsoid;
var batchTable = primitive._batchTable;
var boundingSpheres = primitive._instanceBoundingSpheres;
var length = boundingSpheres.length;
for (var i = 0; i < length; ++i) {
var boundingSphere = boundingSpheres[i];
if (!defined(boundingSphere)) {
continue;
}
var modelMatrix = primitive.modelMatrix;
if (defined(modelMatrix)) {
boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, boundingSphere);
}
if (hasDistanceDisplayCondition) {
var center = boundingSphere.center;
var radius = boundingSphere.radius;
var encodedCenter = EncodedCartesian3.fromCartesian(center, scratchBoundingSphereCenterEncoded);
batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high);
batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low);
var cartographic = ellipsoid.cartesianToCartographic(center, scratchBoundingSphereCartographic);
var center2D = projection.project(cartographic, scratchBoundingSphereCenter2D);
encodedCenter = EncodedCartesian3.fromCartesian(center2D, scratchBoundingSphereCenterEncoded);
batchTable.setBatchedAttribute(i, center2DHighIndex, encodedCenter.high);
batchTable.setBatchedAttribute(i, center2DLowIndex, encodedCenter.low);
batchTable.setBatchedAttribute(i, radiusIndex, radius);
}
}
primitive._batchTableBoundingSpheresUpdated = true;
}
function createVertexArray(primitive, frameState) {
var attributeLocations = primitive._attributeLocations;
var geometries = primitive._geometries;
var scene3DOnly = frameState.scene3DOnly;
var context = frameState.context;
var va = [];
var length = geometries.length;
for (var i = 0; i < length; ++i) {
var geometry = geometries[i];
va.push(VertexArray.fromGeometry({
context : context,
geometry : geometry,
attributeLocations : attributeLocations,
bufferUsage : BufferUsage.STATIC_DRAW,
interleave : primitive._interleave
}));
if (defined(primitive._createBoundingVolumeFunction)) {
primitive._createBoundingVolumeFunction(frameState, geometry);
} else {
primitive._boundingSpheres.push(BoundingSphere.clone(geometry.boundingSphere));
primitive._boundingSphereWC.push(new BoundingSphere());
if (!scene3DOnly) {
var center = geometry.boundingSphereCV.center;
var x = center.x;
var y = center.y;
var z = center.z;
center.x = z;
center.y = x;
center.z = y;
primitive._boundingSphereCV.push(BoundingSphere.clone(geometry.boundingSphereCV));
primitive._boundingSphere2D.push(new BoundingSphere());
primitive._boundingSphereMorph.push(new BoundingSphere());
}
}
}
primitive._va = va;
primitive._primitiveType = geometries[0].primitiveType;
if (primitive.releaseGeometryInstances) {
primitive.geometryInstances = undefined;
}
primitive._geometries = undefined;
setReady(primitive, frameState, PrimitiveState.COMPLETE, undefined);
}
function createRenderStates(primitive, context, appearance, twoPasses) {
var renderState = appearance.getRenderState();
var rs;
if (twoPasses) {
rs = clone(renderState, false);
rs.cull = {
enabled : true,
face : CullFace.BACK
};
primitive._frontFaceRS = RenderState.fromCache(rs);
rs.cull.face = CullFace.FRONT;
primitive._backFaceRS = RenderState.fromCache(rs);
} else {
primitive._frontFaceRS = RenderState.fromCache(renderState);
primitive._backFaceRS = primitive._frontFaceRS;
}
if (primitive.allowPicking) {
if (twoPasses) {
rs = clone(renderState, false);
rs.cull = {
enabled : false
};
primitive._pickRS = RenderState.fromCache(rs);
} else {
primitive._pickRS = primitive._frontFaceRS;
}
} else {
rs = clone(renderState, false);
rs.colorMask = {
red : false,
green : false,
blue : false,
alpha : false
};
if (twoPasses) {
rs.cull = {
enabled : false
};
primitive._pickRS = RenderState.fromCache(rs);
} else {
primitive._pickRS = RenderState.fromCache(rs);
}
}
}
function createShaderProgram(primitive, frameState, appearance) {
var context = frameState.context;
var attributeLocations = primitive._attributeLocations;
var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource);
vs = Primitive._appendShowToShader(primitive, vs);
vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly);
vs = Primitive._updateColorAttribute(primitive, vs);
vs = modifyForEncodedNormals(primitive, vs);
vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
var fs = appearance.getFragmentShaderSource();
// Create pick program
if (primitive.allowPicking) {
var vsPick = ShaderSource.createPickVertexShaderSource(vs);
vsPick = Primitive._updatePickColorAttribute(vsPick);
primitive._pickSP = ShaderProgram.replaceCache({
context : context,
shaderProgram : primitive._pickSP,
vertexShaderSource : vsPick,
fragmentShaderSource : ShaderSource.createPickFragmentShaderSource(fs, 'varying'),
attributeLocations : attributeLocations
});
} else {
primitive._pickSP = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
}
validateShaderMatching(primitive._pickSP, attributeLocations);
primitive._sp = ShaderProgram.replaceCache({
context : context,
shaderProgram : primitive._sp,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
validateShaderMatching(primitive._sp, attributeLocations);
}
var modifiedModelViewScratch = new Matrix4();
var rtcScratch = new Cartesian3();
function createCommands(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands, frameState) {
// Create uniform map by combining uniforms from the appearance and material if either have uniforms.
var materialUniformMap = defined(material) ? material._uniforms : undefined;
var appearanceUniformMap = {};
var appearanceUniforms = appearance.uniforms;
if (defined(appearanceUniforms)) {
// Convert to uniform map of functions for the renderer
for (var name in appearanceUniforms) {
if (appearanceUniforms.hasOwnProperty(name)) {
if (defined(materialUniformMap) && defined(materialUniformMap[name])) {
// Later, we could rename uniforms behind-the-scenes if needed.
throw new DeveloperError('Appearance and material have a uniform with the same name: ' + name);
}
appearanceUniformMap[name] = getUniformFunction(appearanceUniforms, name);
}
}
}
var uniforms = combine(appearanceUniformMap, materialUniformMap);
uniforms = primitive._batchTable.getUniformMapCallback()(uniforms);
if (defined(primitive.rtcCenter)) {
uniforms.u_modifiedModelView = function() {
var viewMatrix = frameState.context.uniformState.view;
Matrix4.multiply(viewMatrix, primitive._modelMatrix, modifiedModelViewScratch);
Matrix4.multiplyByPoint(modifiedModelViewScratch, primitive.rtcCenter, rtcScratch);
Matrix4.setTranslation(modifiedModelViewScratch, rtcScratch, modifiedModelViewScratch);
return modifiedModelViewScratch;
};
}
var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
colorCommands.length = primitive._va.length * (twoPasses ? 2 : 1);
pickCommands.length = primitive._va.length;
var length = colorCommands.length;
var m = 0;
var vaIndex = 0;
for (var i = 0; i < length; ++i) {
var colorCommand;
if (twoPasses) {
colorCommand = colorCommands[i];
if (!defined(colorCommand)) {
colorCommand = colorCommands[i] = new DrawCommand({
owner : primitive,
primitiveType : primitive._primitiveType
});
}
colorCommand.vertexArray = primitive._va[vaIndex];
colorCommand.renderState = primitive._backFaceRS;
colorCommand.shaderProgram = primitive._sp;
colorCommand.uniformMap = uniforms;
colorCommand.pass = pass;
++i;
}
colorCommand = colorCommands[i];
if (!defined(colorCommand)) {
colorCommand = colorCommands[i] = new DrawCommand({
owner : primitive,
primitiveType : primitive._primitiveType
});
}
colorCommand.vertexArray = primitive._va[vaIndex];
colorCommand.renderState = primitive._frontFaceRS;
colorCommand.shaderProgram = primitive._sp;
colorCommand.uniformMap = uniforms;
colorCommand.pass = pass;
var pickCommand = pickCommands[m];
if (!defined(pickCommand)) {
pickCommand = pickCommands[m] = new DrawCommand({
owner : primitive,
primitiveType : primitive._primitiveType
});
}
pickCommand.vertexArray = primitive._va[vaIndex];
pickCommand.renderState = primitive._pickRS;
pickCommand.shaderProgram = primitive._pickSP;
pickCommand.uniformMap = uniforms;
pickCommand.pass = pass;
++m;
++vaIndex;
}
}
function updateBoundingVolumes(primitive, frameState) {
// Update bounding volumes for primitives that are sized in pixels.
// The pixel size in meters varies based on the distance from the camera.
var pixelSize = primitive.appearance.pixelSize;
if (defined(pixelSize)) {
var length = primitive._boundingSpheres.length;
for (var i = 0; i < length; ++i) {
var boundingSphere = primitive._boundingSpheres[i];
var boundingSphereWC = primitive._boundingSphereWC[i];
var pixelSizeInMeters = frameState.camera.getPixelSize(boundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
var sizeInMeters = pixelSizeInMeters * pixelSize;
boundingSphereWC.radius = boundingSphere.radius + sizeInMeters;
}
}
}
function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) {
throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.');
}
updateBoundingVolumes(primitive, frameState);
if (!Matrix4.equals(modelMatrix, primitive._modelMatrix)) {
Matrix4.clone(modelMatrix, primitive._modelMatrix);
var length = primitive._boundingSpheres.length;
for (var i = 0; i < length; ++i) {
var boundingSphere = primitive._boundingSpheres[i];
if (defined(boundingSphere)) {
primitive._boundingSphereWC[i] = BoundingSphere.transform(boundingSphere, modelMatrix, primitive._boundingSphereWC[i]);
if (!frameState.scene3DOnly) {
primitive._boundingSphere2D[i] = BoundingSphere.clone(primitive._boundingSphereCV[i], primitive._boundingSphere2D[i]);
primitive._boundingSphere2D[i].center.x = 0.0;
primitive._boundingSphereMorph[i] = BoundingSphere.union(primitive._boundingSphereWC[i], primitive._boundingSphereCV[i]);
}
}
}
}
var boundingSpheres;
if (frameState.mode === SceneMode.SCENE3D) {
boundingSpheres = primitive._boundingSphereWC;
} else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
boundingSpheres = primitive._boundingSphereCV;
} else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) {
boundingSpheres = primitive._boundingSphere2D;
} else if (defined(primitive._boundingSphereMorph)) {
boundingSpheres = primitive._boundingSphereMorph;
}
var commandList = frameState.commandList;
var passes = frameState.passes;
if (passes.render) {
var castShadows = ShadowMode.castShadows(primitive.shadows);
var receiveShadows = ShadowMode.receiveShadows(primitive.shadows);
var colorLength = colorCommands.length;
for (var j = 0; j < colorLength; ++j) {
var sphereIndex = twoPasses ? Math.floor(j / 2) : j;
var colorCommand = colorCommands[j];
colorCommand.modelMatrix = modelMatrix;
colorCommand.boundingVolume = boundingSpheres[sphereIndex];
colorCommand.cull = cull;
colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
colorCommand.castShadows = castShadows;
colorCommand.receiveShadows = receiveShadows;
commandList.push(colorCommand);
}
}
if (passes.pick) {
var pickLength = pickCommands.length;
for (var k = 0; k < pickLength; ++k) {
var pickCommand = pickCommands[k];
pickCommand.modelMatrix = modelMatrix;
pickCommand.boundingVolume = boundingSpheres[k];
pickCommand.cull = cull;
commandList.push(pickCommand);
}
}
}
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {DeveloperError} All instance geometries must have the same primitiveType.
* @exception {DeveloperError} Appearance and material have a uniform with the same name.
* @exception {DeveloperError} Primitive.modelMatrix is only supported in 3D mode.
* @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.
*/
Primitive.prototype.update = function(frameState) {
if (((!defined(this.geometryInstances)) && (this._va.length === 0)) ||
(defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length === 0) ||
(!defined(this.appearance)) ||
(frameState.mode !== SceneMode.SCENE3D && frameState.scene3DOnly) ||
(!frameState.passes.render && !frameState.passes.pick)) {
return;
}
if (defined(this._error)) {
throw this._error;
}
if (defined(this.rtcCenter) && !frameState.scene3DOnly) {
throw new DeveloperError('RTC rendering is only available for 3D only scenes.');
}
if (this._state === PrimitiveState.FAILED) {
return;
}
var context = frameState.context;
if (!defined(this._batchTable)) {
createBatchTable(this, context);
}
if (this._batchTable.attributes.length > 0) {
if (ContextLimits.maximumVertexTextureImageUnits === 0) {
throw new RuntimeError('Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.');
}
this._batchTable.update(frameState);
}
if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) {
if (this.asynchronous) {
loadAsynchronous(this, frameState);
} else {
loadSynchronous(this, frameState);
}
}
if (this._state === PrimitiveState.COMBINED) {
updateBatchTableBoundingSpheres(this, frameState);
createVertexArray(this, frameState);
}
if (!this.show || this._state !== PrimitiveState.COMPLETE) {
return;
}
// Create or recreate render state and shader program if appearance/material changed
var appearance = this.appearance;
var material = appearance.material;
var createRS = false;
var createSP = false;
if (this._appearance !== appearance) {
this._appearance = appearance;
this._material = material;
createRS = true;
createSP = true;
} else if (this._material !== material ) {
this._material = material;
createSP = true;
}
var translucent = this._appearance.isTranslucent();
if (this._translucent !== translucent) {
this._translucent = translucent;
createRS = true;
}
if (defined(this._material)) {
this._material.update(context);
}
var twoPasses = appearance.closed && translucent;
if (createRS) {
var rsFunc = defaultValue(this._createRenderStatesFunction, createRenderStates);
rsFunc(this, context, appearance, twoPasses);
}
if (createSP) {
var spFunc = defaultValue(this._createShaderProgramFunction, createShaderProgram);
spFunc(this, frameState, appearance);
}
if (createRS || createSP) {
var commandFunc = defaultValue(this._createCommandsFunction, createCommands);
commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState);
}
var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands);
updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses);
};
function createGetFunction(batchTable, instanceIndex, attributeIndex) {
return function() {
var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex);
var attribute = batchTable.attributes[attributeIndex];
var componentsPerAttribute = attribute.componentsPerAttribute;
var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute);
if (defined(attributeValue.constructor.pack)) {
attributeValue.constructor.pack(attributeValue, value, 0);
} else {
value[0] = attributeValue;
}
return value;
};
}
function createSetFunction(batchTable, instanceIndex, attributeIndex) {
return function(value) {
if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) {
throw new DeveloperError('value must be and array with length between 1 and 4.');
}
var attributeValue = getAttributeValue(value);
batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue);
};
}
function createBoundingSphereProperties(primitive, properties, index) {
properties.boundingSphere = {
get : function() {
return primitive._instanceBoundingSpheres[index];
}
};
properties.boundingSphereCV = {
get : function() {
return primitive._instanceBoundingSpheresCV[index];
}
};
}
/**
* Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
*
* @param {Object} id The id of the {@link GeometryInstance}.
* @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
*
* @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
*
* @example
* var attributes = primitive.getGeometryInstanceAttributes('an id');
* attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
* attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
* attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(100.0, 10000.0);
*/
Primitive.prototype.getGeometryInstanceAttributes = function(id) {
if (!defined(id)) {
throw new DeveloperError('id is required');
}
if (!defined(this._batchTable)) {
throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
}
var index = -1;
var lastIndex = this._lastPerInstanceAttributeIndex;
var ids = this._instanceIds;
var length = ids.length;
for (var i = 0; i < length; ++i) {
var curIndex = (lastIndex + i) % length;
if (id === ids[curIndex]) {
index = curIndex;
break;
}
}
if (index === -1) {
return undefined;
}
var attributes = this._perInstanceAttributeCache[index];
if (defined(attributes)) {
return attributes;
}
var batchTable = this._batchTable;
var perInstanceAttributeIndices = this._batchTableAttributeIndices;
attributes = {};
var properties = {};
for (var name in perInstanceAttributeIndices) {
if (perInstanceAttributeIndices.hasOwnProperty(name)) {
var attributeIndex = perInstanceAttributeIndices[name];
properties[name] = {
get : createGetFunction(batchTable, index, attributeIndex)
};
var createSetter = true;
var readOnlyAttributes = this._readOnlyInstanceAttributes;
if (createSetter && defined(readOnlyAttributes)) {
length = readOnlyAttributes.length;
for (var k = 0; k < length; ++k) {
if (name === readOnlyAttributes[k]) {
createSetter = false;
break;
}
}
}
if (createSetter) {
properties[name].set = createSetFunction(batchTable, index, attributeIndex);
}
}
}
createBoundingSphereProperties(this, properties, index);
defineProperties(attributes, properties);
this._lastPerInstanceAttributeIndex = index;
this._perInstanceAttributeCache[index] = attributes;
return attributes;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see Primitive#destroy
*/
Primitive.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* e = e && e.destroy();
*
* @see Primitive#isDestroyed
*/
Primitive.prototype.destroy = function() {
var length;
var i;
this._sp = this._sp && this._sp.destroy();
this._pickSP = this._pickSP && this._pickSP.destroy();
var va = this._va;
length = va.length;
for (i = 0; i < length; ++i) {
va[i].destroy();
}
this._va = undefined;
var pickIds = this._pickIds;
length = pickIds.length;
for (i = 0; i < length; ++i) {
pickIds[i].destroy();
}
this._pickIds = undefined;
this._batchTable = this._batchTable && this._batchTable.destroy();
//These objects may be fairly large and reference other large objects (like Entities)
//We explicitly set them to undefined here so that the memory can be freed
//even if a reference to the destroyed Primitive has been kept around.
this._instanceIds = undefined;
this._perInstanceAttributeCache = undefined;
this._attributeLocations = undefined;
return destroyObject(this);
};
function setReady(primitive, frameState, state, error) {
primitive._error = error;
primitive._state = state;
frameState.afterRender.push(function() {
primitive._ready = primitive._state === PrimitiveState.COMPLETE || primitive._state === PrimitiveState.FAILED;
if (!defined(error)) {
primitive._readyPromise.resolve(primitive);
} else {
primitive._readyPromise.reject(error);
}
});
}
return Primitive;
});
/*global define*/
define('DataSources/ColorMaterialProperty',[
'../Core/Color',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Color,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
/**
* A {@link MaterialProperty} that maps to solid color {@link Material} uniforms.
*
* @param {Property} [color=Color.WHITE] The {@link Color} Property to be used.
*
* @alias ColorMaterialProperty
* @constructor
*/
function ColorMaterialProperty(color) {
this._definitionChanged = new Event();
this._color = undefined;
this._colorSubscription = undefined;
this.color = color;
}
defineProperties(ColorMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof ColorMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._color);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof ColorMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the {@link Color} {@link Property}.
* @memberof ColorMaterialProperty.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
ColorMaterialProperty.prototype.getType = function(time) {
return 'Color';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ColorMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.color = Property.getValueOrClonedDefault(this._color, time, Color.WHITE, result.color);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
ColorMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof ColorMaterialProperty && //
Property.equals(this._color, other._color));
};
return ColorMaterialProperty;
});
/*global define*/
define('DataSources/dynamicGeometryGetBoundingSphere',[
'../Core/BoundingSphere',
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Matrix4',
'./BoundingSphereState'
], function(
BoundingSphere,
defaultValue,
defined,
DeveloperError,
Matrix4,
BoundingSphereState) {
'use strict';
/**
* @private
*/
function dynamicGeometryGetBoundingSphere(entity, primitive, outlinePrimitive, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var attributes;
var modelMatrix;
//Outline and Fill geometries have the same bounding sphere, so just use whichever one is defined and ready
if (defined(primitive) && primitive.show && primitive.ready) {
attributes = primitive.getGeometryInstanceAttributes(entity);
if (defined(attributes) && defined(attributes.boundingSphere)) {
modelMatrix = defaultValue(primitive.modelMatrix, Matrix4.IDENTITY);
BoundingSphere.transform(attributes.boundingSphere, modelMatrix, result);
return BoundingSphereState.DONE;
}
}
if (defined(outlinePrimitive) && outlinePrimitive.show && outlinePrimitive.ready) {
attributes = outlinePrimitive.getGeometryInstanceAttributes(entity);
if (defined(attributes) && defined(attributes.boundingSphere)) {
modelMatrix = defaultValue(outlinePrimitive.modelMatrix, Matrix4.IDENTITY);
BoundingSphere.transform(attributes.boundingSphere, modelMatrix, result);
return BoundingSphereState.DONE;
}
}
if ((defined(primitive) && !primitive.ready) || (defined(outlinePrimitive) && !outlinePrimitive.ready)) {
return BoundingSphereState.PENDING;
}
return BoundingSphereState.FAILED;
}
return dynamicGeometryGetBoundingSphere;
});
/*global define*/
define('DataSources/MaterialProperty',[
'../Core/Color',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Scene/Material'
], function(
Color,
defined,
defineProperties,
DeveloperError,
Material) {
'use strict';
/**
* The interface for all {@link Property} objects that represent {@link Material} uniforms.
* This type defines an interface and cannot be instantiated directly.
*
* @alias MaterialProperty
* @constructor
*
* @see ColorMaterialProperty
* @see CompositeMaterialProperty
* @see GridMaterialProperty
* @see ImageMaterialProperty
* @see PolylineGlowMaterialProperty
* @see PolylineOutlineMaterialProperty
* @see StripeMaterialProperty
*/
function MaterialProperty() {
DeveloperError.throwInstantiationError();
}
defineProperties(MaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof MaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof MaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Gets the {@link Material} type at the provided time.
* @function
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
MaterialProperty.prototype.getType = DeveloperError.throwInstantiationError;
/**
* Gets the value of the property at the provided time.
* @function
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
MaterialProperty.prototype.getValue = DeveloperError.throwInstantiationError;
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
* @function
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
MaterialProperty.prototype.equals = DeveloperError.throwInstantiationError;
/**
* @private
*/
MaterialProperty.getValue = function(time, materialProperty, material) {
var type;
if (defined(materialProperty)) {
type = materialProperty.getType(time);
if (defined(type)) {
if (!defined(material) || (material.type !== type)) {
material = Material.fromType(type);
}
materialProperty.getValue(time, material.uniforms);
return material;
}
}
if (!defined(material) || (material.type !== Material.ColorType)) {
material = Material.fromType(Material.ColorType);
}
Color.clone(Color.WHITE, material.uniforms.color);
return material;
};
return MaterialProperty;
});
/*global define*/
define('DataSources/BoxGeometryUpdater',[
'../Core/BoxGeometry',
'../Core/BoxOutlineGeometry',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
BoxGeometry,
BoxOutlineGeometry,
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
ShowGeometryInstanceAttribute,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.dimensions = undefined;
}
/**
* A {@link GeometryUpdater} for boxes.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias BoxGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function BoxGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(BoxGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'box', entity.box, undefined);
}
defineProperties(BoxGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof BoxGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof BoxGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(BoxGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof BoxGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
value : true
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof BoxGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
BoxGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
BoxGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
BoxGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : BoxGeometry.fromDimensions(this._options),
modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
BoxGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : BoxOutlineGeometry.fromDimensions(this._options),
modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
BoxGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
BoxGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
BoxGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'box')) {
return;
}
var box = this._entity.box;
if (!defined(box)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = box.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = box.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var dimensions = box.dimensions;
var position = entity.position;
var show = box.show;
if (!defined(dimensions) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(box.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(box.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(box.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(box.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(box.distanceDisplayCondition, defaultDistanceDisplayCondition);
var outlineWidth = box.outlineWidth;
this._fillEnabled = fillEnabled;
this._outlineEnabled = outlineEnabled;
if (!position.isConstant || //
!Property.isConstant(entity.orientation) || //
!dimensions.isConstant || //
!Property.isConstant(outlineWidth)) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.dimensions = dimensions.getValue(Iso8601.MINIMUM_VALUE, options.dimensions);
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
BoxGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, geometryUpdater) {
this._primitives = primitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._primitive = undefined;
this._outlinePrimitive = undefined;
var geometryUpdater = this._geometryUpdater;
var entity = geometryUpdater._entity;
var box = entity.box;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(box.show, time, true)) {
return;
}
var options = this._options;
var modelMatrix = entity._getModelMatrix(time);
var dimensions = Property.getValueOrUndefined(box.dimensions, time, options.dimensions);
if (!defined(modelMatrix) || !defined(dimensions)) {
return;
}
options.dimensions = dimensions;
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (Property.getValueOrDefault(box.fill, time, true)) {
var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
this._material = material;
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : true
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : BoxGeometry.fromDimensions(options),
modelMatrix : modelMatrix,
attributes : {
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
if (Property.getValueOrDefault(box.outline, time, false)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(box.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(box.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : BoxOutlineGeometry.fromDimensions(options),
modelMatrix : modelMatrix,
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return BoxGeometryUpdater;
});
/*global define*/
define('DataSources/ImageMaterialProperty',[
'../Core/Cartesian2',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Cartesian2,
Color,
defaultValue,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
var defaultRepeat = new Cartesian2(1, 1);
var defaultTransparent = false;
var defaultColor = Color.WHITE;
/**
* A {@link MaterialProperty} that maps to image {@link Material} uniforms.
* @alias ImageMaterialProperty
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.image] A Property specifying the Image, URL, Canvas, or Video.
* @param {Property} [options.repeat=new Cartesian2(1.0, 1.0)] A {@link Cartesian2} Property specifying the number of times the image repeats in each direction.
* @param {Property} [options.color=Color.WHITE] The color applied to the image
* @param {Property} [options.transparent=false] Set to true when the image has transparency (for example, when a png has transparent sections)
*/
function ImageMaterialProperty(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._image = undefined;
this._imageSubscription = undefined;
this._repeat = undefined;
this._repeatSubscription = undefined;
this._color = undefined;
this._colorSubscription = undefined;
this._transparent = undefined;
this._transparentSubscription = undefined;
this.image = options.image;
this.repeat = options.repeat;
this.color = options.color;
this.transparent = options.transparent;
}
defineProperties(ImageMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof ImageMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._image) && Property.isConstant(this._repeat);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof ImageMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying Image, URL, Canvas, or Video to use.
* @memberof ImageMaterialProperty.prototype
* @type {Property}
*/
image : createPropertyDescriptor('image'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the number of times the image repeats in each direction.
* @memberof ImageMaterialProperty.prototype
* @type {Property}
* @default new Cartesian2(1, 1)
*/
repeat : createPropertyDescriptor('repeat'),
/**
* Gets or sets the Color Property specifying the desired color applied to the image.
* @memberof ImageMaterialProperty.prototype
* @type {Property}
* @default 1.0
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the Boolean Property specifying whether the image has transparency
* @memberof ImageMaterialProperty.prototype
* @type {Property}
* @default 1.0
*/
transparent : createPropertyDescriptor('transparent')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
ImageMaterialProperty.prototype.getType = function(time) {
return 'Image';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ImageMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.image = Property.getValueOrUndefined(this._image, time);
result.repeat = Property.getValueOrClonedDefault(this._repeat, time, defaultRepeat, result.repeat);
result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
if (Property.getValueOrDefault(this._transparent, time, defaultTransparent)) {
result.color.alpha = Math.min(0.99, result.color.alpha);
}
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
ImageMaterialProperty.prototype.equals = function(other) {
return this === other ||
(other instanceof ImageMaterialProperty &&
Property.equals(this._image, other._image) &&
Property.equals(this._color, other._color) &&
Property.equals(this._transparent, other._transparent) &&
Property.equals(this._repeat, other._repeat));
};
return ImageMaterialProperty;
});
/*global define*/
define('DataSources/createMaterialPropertyDescriptor',[
'../Core/Color',
'../Core/DeveloperError',
'./ColorMaterialProperty',
'./createPropertyDescriptor',
'./ImageMaterialProperty'
], function(
Color,
DeveloperError,
ColorMaterialProperty,
createPropertyDescriptor,
ImageMaterialProperty) {
'use strict';
function createMaterialProperty(value) {
if (value instanceof Color) {
return new ColorMaterialProperty(value);
}
if (typeof value === 'string' || value instanceof HTMLCanvasElement || value instanceof HTMLVideoElement) {
var result = new ImageMaterialProperty();
result.image = value;
return result;
}
throw new DeveloperError('Unable to infer material type: ' + value);
}
/**
* @private
*/
function createMaterialPropertyDescriptor(name, configurable) {
return createPropertyDescriptor(name, configurable, createMaterialProperty);
}
return createMaterialPropertyDescriptor;
});
/*global define*/
define('DataSources/BoxGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a box. The center position and orientation are determined by the containing {@link Entity}.
*
* @alias BoxGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.dimensions] A {@link Cartesian3} Property specifying the length, width, and height of the box.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the box.
* @param {Property} [options.fill=true] A boolean Property specifying whether the box is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the box.
* @param {Property} [options.outline=false] A boolean Property specifying whether the box is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the box casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this box will be displayed.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo}
*/
function BoxGraphics(options) {
this._dimensions = undefined;
this._dimensionsSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(BoxGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof BoxGraphics.prototype
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the box.
* @memberof BoxGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets {@link Cartesian3} Property property specifying the length, width, and height of the box.
* @memberof BoxGraphics.prototype
* @type {Property}
*/
dimensions : createPropertyDescriptor('dimensions'),
/**
* Gets or sets the material used to fill the box.
* @memberof BoxGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the boolean Property specifying whether the box is filled with the provided material.
* @memberof BoxGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the box is outlined.
* @memberof BoxGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof BoxGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof BoxGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Get or sets the enum Property specifying whether the box
* casts or receives shadows from each light source.
* @memberof BoxGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this box will be displayed.
* @memberof BoxGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {BoxGraphics} [result] The object onto which to store the result.
* @returns {BoxGraphics} The modified result parameter or a new instance if one was not provided.
*/
BoxGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new BoxGraphics(this);
}
result.dimensions = this.dimensions;
result.show = this.show;
result.material = this.material;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {BoxGraphics} source The object to be merged into this object.
*/
BoxGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.dimensions = defaultValue(this.dimensions, source.dimensions);
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return BoxGraphics;
});
/*global define*/
define('DataSources/CallbackProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event'
], function(
defined,
defineProperties,
DeveloperError,
Event) {
'use strict';
/**
* A {@link Property} whose value is lazily evaluated by a callback function.
*
* @alias CallbackProperty
* @constructor
*
* @param {CallbackProperty~Callback} callback The function to be called when the property is evaluated.
* @param {Boolean} isConstant true
when the callback function returns the same value every time, false
if the value will change.
*/
function CallbackProperty(callback, isConstant) {
this._callback = undefined;
this._isConstant = undefined;
this._definitionChanged = new Event();
this.setCallback(callback, isConstant);
}
defineProperties(CallbackProperty.prototype, {
/**
* Gets a value indicating if this property is constant.
* @memberof CallbackProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._isConstant;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setCallback is called.
* @memberof CallbackProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
}
});
/**
* Gets the value of the property.
*
* @param {JulianDate} [time] The time for which to retrieve the value. This parameter is unused since the value does not change with respect to time.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied or is unsupported.
*/
CallbackProperty.prototype.getValue = function(time, result) {
return this._callback(time, result);
};
/**
* Sets the callback to be used.
*
* @param {CallbackProperty~Callback} callback The function to be called when the property is evaluated.
* @param {Boolean} isConstant true
when the callback function returns the same value every time, false
if the value will change.
*/
CallbackProperty.prototype.setCallback = function(callback, isConstant) {
if (!defined(callback)) {
throw new DeveloperError('callback is required.');
}
if (!defined(isConstant)) {
throw new DeveloperError('isConstant is required.');
}
var changed = this._callback !== callback || this._isConstant !== isConstant;
this._callback = callback;
this._isConstant = isConstant;
if (changed) {
this._definitionChanged.raiseEvent(this);
}
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
CallbackProperty.prototype.equals = function(other) {
return this === other || (other instanceof CallbackProperty && this._callback === other._callback && this._isConstant === other._isConstant);
};
/**
* A function that returns the value of the property.
* @callback CallbackProperty~Callback
*
* @param {JulianDate} [time] The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied or is unsupported.
*/
return CallbackProperty;
});
/*global define*/
define('DataSources/CheckerboardMaterialProperty',[
'../Core/Cartesian2',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Cartesian2,
Color,
defaultValue,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
var defaultEvenColor = Color.WHITE;
var defaultOddColor = Color.BLACK;
var defaultRepeat = new Cartesian2(2.0, 2.0);
/**
* A {@link MaterialProperty} that maps to checkerboard {@link Material} uniforms.
* @alias CheckerboardMaterialProperty
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.evenColor=Color.WHITE] A Property specifying the first {@link Color}.
* @param {Property} [options.oddColor=Color.BLACK] A Property specifying the second {@link Color}.
* @param {Property} [options.repeat=new Cartesian2(2.0, 2.0)] A {@link Cartesian2} Property specifying how many times the tiles repeat in each direction.
*/
function CheckerboardMaterialProperty(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._evenColor = undefined;
this._evenColorSubscription = undefined;
this._oddColor = undefined;
this._oddColorSubscription = undefined;
this._repeat = undefined;
this._repeatSubscription = undefined;
this.evenColor = options.evenColor;
this.oddColor = options.oddColor;
this.repeat = options.repeat;
}
defineProperties(CheckerboardMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof CheckerboardMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._evenColor) && //
Property.isConstant(this._oddColor) && //
Property.isConstant(this._repeat);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof CheckerboardMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the first {@link Color}.
* @memberof CheckerboardMaterialProperty.prototype
* @type {Property}
* @default Color.WHITE
*/
evenColor : createPropertyDescriptor('evenColor'),
/**
* Gets or sets the Property specifying the second {@link Color}.
* @memberof CheckerboardMaterialProperty.prototype
* @type {Property}
* @default Color.BLACK
*/
oddColor : createPropertyDescriptor('oddColor'),
/**
* Gets or sets the {@link Cartesian2} Property specifying how many times the tiles repeat in each direction.
* @memberof CheckerboardMaterialProperty.prototype
* @type {Property}
* @default new Cartesian2(2.0, 2.0)
*/
repeat : createPropertyDescriptor('repeat')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
CheckerboardMaterialProperty.prototype.getType = function(time) {
return 'Checkerboard';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
CheckerboardMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.lightColor = Property.getValueOrClonedDefault(this._evenColor, time, defaultEvenColor, result.lightColor);
result.darkColor = Property.getValueOrClonedDefault(this._oddColor, time, defaultOddColor, result.darkColor);
result.repeat = Property.getValueOrDefault(this._repeat, time, defaultRepeat);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
CheckerboardMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof CheckerboardMaterialProperty && //
Property.equals(this._evenColor, other._evenColor) && //
Property.equals(this._oddColor, other._oddColor) && //
Property.equals(this._repeat, other._repeat));
};
return CheckerboardMaterialProperty;
});
/*global define*/
define('DataSources/PositionProperty',[
'../Core/Cartesian3',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Matrix3',
'../Core/ReferenceFrame',
'../Core/Transforms'
], function(
Cartesian3,
defined,
defineProperties,
DeveloperError,
Matrix3,
ReferenceFrame,
Transforms) {
'use strict';
/**
* The interface for all {@link Property} objects that define a world
* location as a {@link Cartesian3} with an associated {@link ReferenceFrame}.
* This type defines an interface and cannot be instantiated directly.
*
* @alias PositionProperty
* @constructor
*
* @see CompositePositionProperty
* @see ConstantPositionProperty
* @see SampledPositionProperty
* @see TimeIntervalCollectionPositionProperty
*/
function PositionProperty() {
DeveloperError.throwInstantiationError();
}
defineProperties(PositionProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof PositionProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof PositionProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the reference frame that the position is defined in.
* @memberof PositionProperty.prototype
* @type {ReferenceFrame}
*/
referenceFrame : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Gets the value of the property at the provided time in the fixed frame.
* @function
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PositionProperty.prototype.getValue = DeveloperError.throwInstantiationError;
/**
* Gets the value of the property at the provided time and in the provided reference frame.
* @function
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PositionProperty.prototype.getValueInReferenceFrame = DeveloperError.throwInstantiationError;
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
* @function
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PositionProperty.prototype.equals = DeveloperError.throwInstantiationError;
var scratchMatrix3 = new Matrix3();
/**
* @private
*/
PositionProperty.convertToReferenceFrame = function(time, value, inputFrame, outputFrame, result) {
if (!defined(value)) {
return value;
}
if (!defined(result)){
result = new Cartesian3();
}
if (inputFrame === outputFrame) {
return Cartesian3.clone(value, result);
}
var icrfToFixed = Transforms.computeIcrfToFixedMatrix(time, scratchMatrix3);
if (!defined(icrfToFixed)) {
icrfToFixed = Transforms.computeTemeToPseudoFixedMatrix(time, scratchMatrix3);
}
if (inputFrame === ReferenceFrame.INERTIAL) {
return Matrix3.multiplyByVector(icrfToFixed, value, result);
}
if (inputFrame === ReferenceFrame.FIXED) {
return Matrix3.multiplyByVector(Matrix3.transpose(icrfToFixed, scratchMatrix3), value, result);
}
};
return PositionProperty;
});
/*global define*/
define('DataSources/ConstantPositionProperty',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/ReferenceFrame',
'./PositionProperty'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
ReferenceFrame,
PositionProperty) {
'use strict';
/**
* A {@link PositionProperty} whose value does not change in respect to the
* {@link ReferenceFrame} in which is it defined.
*
* @alias ConstantPositionProperty
* @constructor
*
* @param {Cartesian3} [value] The property value.
* @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
*/
function ConstantPositionProperty(value, referenceFrame) {
this._definitionChanged = new Event();
this._value = Cartesian3.clone(value);
this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
}
defineProperties(ConstantPositionProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof ConstantPositionProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return !defined(this._value) || this._referenceFrame === ReferenceFrame.FIXED;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof ConstantPositionProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the reference frame in which the position is defined.
* @memberof ConstantPositionProperty.prototype
* @type {ReferenceFrame}
* @default ReferenceFrame.FIXED;
*/
referenceFrame : {
get : function() {
return this._referenceFrame;
}
}
});
/**
* Gets the value of the property at the provided time in the fixed frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ConstantPositionProperty.prototype.getValue = function(time, result) {
return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
};
/**
* Sets the value of the property.
*
* @param {Cartesian3} value The property value.
* @param {ReferenceFrame} [referenceFrame=this.referenceFrame] The reference frame in which the position is defined.
*/
ConstantPositionProperty.prototype.setValue = function(value, referenceFrame) {
var definitionChanged = false;
if (!Cartesian3.equals(this._value, value)) {
definitionChanged = true;
this._value = Cartesian3.clone(value);
}
if (defined(referenceFrame) && this._referenceFrame !== referenceFrame) {
definitionChanged = true;
this._referenceFrame = referenceFrame;
}
if (definitionChanged) {
this._definitionChanged.raiseEvent(this);
}
};
/**
* Gets the value of the property at the provided time and in the provided reference frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ConstantPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(referenceFrame)) {
throw new DeveloperError('referenceFrame is required.');
}
return PositionProperty.convertToReferenceFrame(time, this._value, this._referenceFrame, referenceFrame, result);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
ConstantPositionProperty.prototype.equals = function(other) {
return this === other ||
(other instanceof ConstantPositionProperty &&
Cartesian3.equals(this._value, other._value) &&
this._referenceFrame === other._referenceFrame);
};
return ConstantPositionProperty;
});
/*global define*/
define('DataSources/CorridorGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a corridor, which is a shape defined by a centerline and width that
* conforms to the curvature of the globe. It can be placed on the surface or at altitude
* and can optionally be extruded into a volume.
*
* @alias CorridorGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the centerline of the corridor.
* @param {Property} [options.width] A numeric Property specifying the distance between the edges of the corridor.
* @param {Property} [options.cornerType=CornerType.ROUNDED] A {@link CornerType} Property specifying the style of the corners.
* @param {Property} [options.height=0] A numeric Property specifying the altitude of the corridor relative to the ellipsoid surface.
* @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the corridor's extruded face relative to the ellipsoid surface.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the corridor.
* @param {Property} [options.fill=true] A boolean Property specifying whether the corridor is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the corridor.
* @param {Property} [options.outline=false] A boolean Property specifying whether the corridor is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the distance between each latitude and longitude.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the corridor casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this corridor will be displayed.
*
* @see Entity
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo}
*/
function CorridorGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._positions = undefined;
this._positionsSubscription = undefined;
this._height = undefined;
this._heightSubscription = undefined;
this._extrudedHeight = undefined;
this._extrudedHeightSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._width = undefined;
this._widthSubscription = undefined;
this._cornerType = undefined;
this._cornerTypeSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(CorridorGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof CorridorGraphics.prototype
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the corridor.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to fill the corridor.
* @memberof CorridorGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets a Property specifying the array of {@link Cartesian3} positions that define the centerline of the corridor.
* @memberof CorridorGraphics.prototype
* @type {Property}
*/
positions : createPropertyDescriptor('positions'),
/**
* Gets or sets the numeric Property specifying the altitude of the corridor.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default 0.0
*/
height : createPropertyDescriptor('height'),
/**
* Gets or sets the numeric Property specifying the altitude of the corridor extrusion.
* Setting this property creates a corridor shaped volume starting at height and ending
* at this altitude.
* @memberof CorridorGraphics.prototype
* @type {Property}
*/
extrudedHeight : createPropertyDescriptor('extrudedHeight'),
/**
* Gets or sets the numeric Property specifying the sampling distance between each latitude and longitude point.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default {CesiumMath.RADIANS_PER_DEGREE}
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Gets or sets the numeric Property specifying the width of the corridor.
* @memberof CorridorGraphics.prototype
* @type {Property}
*/
width : createPropertyDescriptor('width'),
/**
* Gets or sets the boolean Property specifying whether the corridor is filled with the provided material.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the corridor is outlined.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the {@link CornerType} Property specifying how corners are styled.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default CornerType.ROUNDED
*/
cornerType : createPropertyDescriptor('cornerType'),
/**
* Get or sets the enum Property specifying whether the corridor
* casts or receives shadows from each light source.
* @memberof CorridorGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this corridor will be displayed.
* @memberof CorridorGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {CorridorGraphics} [result] The object onto which to store the result.
* @returns {CorridorGraphics} The modified result parameter or a new instance if one was not provided.
*/
CorridorGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new CorridorGraphics(this);
}
result.show = this.show;
result.material = this.material;
result.positions = this.positions;
result.height = this.height;
result.extrudedHeight = this.extrudedHeight;
result.granularity = this.granularity;
result.width = this.width;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.cornerType = this.cornerType;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {CorridorGraphics} source The object to be merged into this object.
*/
CorridorGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.positions = defaultValue(this.positions, source.positions);
this.height = defaultValue(this.height, source.height);
this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
this.granularity = defaultValue(this.granularity, source.granularity);
this.width = defaultValue(this.width, source.width);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.cornerType = defaultValue(this.cornerType, source.cornerType);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return CorridorGraphics;
});
/*global define*/
define('DataSources/createRawPropertyDescriptor',[
'./createPropertyDescriptor'
], function(
createPropertyDescriptor) {
'use strict';
function createRawProperty(value) {
return value;
}
/**
* @private
*/
function createRawPropertyDescriptor(name, configurable) {
return createPropertyDescriptor(name, configurable, createRawProperty);
}
return createRawPropertyDescriptor;
});
/*global define*/
define('DataSources/CylinderGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a cylinder, truncated cone, or cone defined by a length, top radius, and bottom radius.
* The center position and orientation are determined by the containing {@link Entity}.
*
* @alias CylinderGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.length] A numeric Property specifying the length of the cylinder.
* @param {Property} [options.topRadius] A numeric Property specifying the radius of the top of the cylinder.
* @param {Property} [options.bottomRadius] A numeric Property specifying the radius of the bottom of the cylinder.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the cylinder.
* @param {Property} [options.fill=true] A boolean Property specifying whether the cylinder is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the cylinder.
* @param {Property} [options.outline=false] A boolean Property specifying whether the cylinder is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.numberOfVerticalLines=16] A numeric Property specifying the number of vertical lines to draw along the perimeter for the outline.
* @param {Property} [options.slices=128] The number of edges around the perimeter of the cylinder.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the cylinder casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this cylinder will be displayed.
*/
function CylinderGraphics(options) {
this._length = undefined;
this._lengthSubscription = undefined;
this._topRadius = undefined;
this._topRadiusSubscription = undefined;
this._bottomRadius = undefined;
this._bottomRadiusSubscription = undefined;
this._numberOfVerticalLines = undefined;
this._numberOfVerticalLinesSubscription = undefined;
this._slices = undefined;
this._slicesSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(CylinderGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof CylinderGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the numeric Property specifying the length of the cylinder.
* @memberof CylinderGraphics.prototype
* @type {Property}
*/
length : createPropertyDescriptor('length'),
/**
* Gets or sets the numeric Property specifying the radius of the top of the cylinder.
* @memberof CylinderGraphics.prototype
* @type {Property}
*/
topRadius : createPropertyDescriptor('topRadius'),
/**
* Gets or sets the numeric Property specifying the radius of the bottom of the cylinder.
* @memberof CylinderGraphics.prototype
* @type {Property}
*/
bottomRadius : createPropertyDescriptor('bottomRadius'),
/**
* Gets or sets the Property specifying the number of vertical lines to draw along the perimeter for the outline.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default 16
*/
numberOfVerticalLines : createPropertyDescriptor('numberOfVerticalLines'),
/**
* Gets or sets the Property specifying the number of edges around the perimeter of the cylinder.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default 16
*/
slices : createPropertyDescriptor('slices'),
/**
* Gets or sets the boolean Property specifying the visibility of the cylinder.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to fill the cylinder.
* @memberof CylinderGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the boolean Property specifying whether the cylinder is filled with the provided material.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the boolean Property specifying whether the cylinder is outlined.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Get or sets the enum Property specifying whether the cylinder
* casts or receives shadows from each light source.
* @memberof CylinderGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this cylinder will be displayed.
* @memberof CylinderGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {CylinderGraphics} [result] The object onto which to store the result.
* @returns {CylinderGraphics} The modified result parameter or a new instance if one was not provided.
*/
CylinderGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new CylinderGraphics(this);
}
result.bottomRadius = this.bottomRadius;
result.length = this.length;
result.topRadius = this.topRadius;
result.show = this.show;
result.material = this.material;
result.numberOfVerticalLines = this.numberOfVerticalLines;
result.slices = this.slices;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {CylinderGraphics} source The object to be merged into this object.
*/
CylinderGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.bottomRadius = defaultValue(this.bottomRadius, source.bottomRadius);
this.length = defaultValue(this.length, source.length);
this.topRadius = defaultValue(this.topRadius, source.topRadius);
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.numberOfVerticalLines = defaultValue(this.numberOfVerticalLines, source.numberOfVerticalLines);
this.slices = defaultValue(this.slices, source.slices);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return CylinderGraphics;
});
/*global define*/
define('DataSources/EllipseGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes an ellipse defined by a center point and semi-major and semi-minor axes.
* The ellipse conforms to the curvature of the globe and can be placed on the surface or
* at altitude and can optionally be extruded into a volume.
* The center point is determined by the containing {@link Entity}.
*
* @alias EllipseGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.semiMajorAxis] The numeric Property specifying the semi-major axis.
* @param {Property} [options.semiMinorAxis] The numeric Property specifying the semi-minor axis.
* @param {Property} [options.height=0] A numeric Property specifying the altitude of the ellipse relative to the ellipsoid surface.
* @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the ellipse's extruded face relative to the ellipsoid surface.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the ellipse.
* @param {Property} [options.fill=true] A boolean Property specifying whether the ellipse is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the ellipse.
* @param {Property} [options.outline=false] A boolean Property specifying whether the ellipse is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.numberOfVerticalLines=16] A numeric Property specifying the number of vertical lines to draw along the perimeter for the outline.
* @param {Property} [options.rotation=0.0] A numeric property specifying the rotation of the ellipse counter-clockwise from north.
* @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the ellipse texture counter-clockwise from north.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between points on the ellipse.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the ellipse casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this ellipse will be displayed.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Circles and Ellipses.html|Cesium Sandcastle Circles and Ellipses Demo}
*/
function EllipseGraphics(options) {
this._semiMajorAxis = undefined;
this._semiMajorAxisSubscription = undefined;
this._semiMinorAxis = undefined;
this._semiMinorAxisSubscription = undefined;
this._rotation = undefined;
this._rotationSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._height = undefined;
this._heightSubscription = undefined;
this._extrudedHeight = undefined;
this._extrudedHeightSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._stRotation = undefined;
this._stRotationSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._numberOfVerticalLines = undefined;
this._numberOfVerticalLinesSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(EllipseGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof EllipseGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the numeric Property specifying the semi-major axis.
* @memberof EllipseGraphics.prototype
* @type {Property}
*/
semiMajorAxis : createPropertyDescriptor('semiMajorAxis'),
/**
* Gets or sets the numeric Property specifying the semi-minor axis.
* @memberof EllipseGraphics.prototype
* @type {Property}
*/
semiMinorAxis : createPropertyDescriptor('semiMinorAxis'),
/**
* Gets or sets the numeric property specifying the rotation of the ellipse clockwise from north.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default 0
*/
rotation : createPropertyDescriptor('rotation'),
/**
* Gets or sets the boolean Property specifying the visibility of the ellipse.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to fill the ellipse.
* @memberof EllipseGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the numeric Property specifying the altitude of the ellipse.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default 0.0
*/
height : createPropertyDescriptor('height'),
/**
* Gets or sets the numeric Property specifying the altitude of the ellipse extrusion.
* Setting this property creates volume starting at height and ending at this altitude.
* @memberof EllipseGraphics.prototype
* @type {Property}
*/
extrudedHeight : createPropertyDescriptor('extrudedHeight'),
/**
* Gets or sets the numeric Property specifying the angular distance between points on the ellipse.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default {CesiumMath.RADIANS_PER_DEGREE}
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Gets or sets the numeric property specifying the rotation of the ellipse texture counter-clockwise from north.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default 0
*/
stRotation : createPropertyDescriptor('stRotation'),
/**
* Gets or sets the boolean Property specifying whether the ellipse is filled with the provided material.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the ellipse is outlined.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the numeric Property specifying the number of vertical lines to draw along the perimeter for the outline.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default 16
*/
numberOfVerticalLines : createPropertyDescriptor('numberOfVerticalLines'),
/**
* Get or sets the enum Property specifying whether the ellipse
* casts or receives shadows from each light source.
* @memberof EllipseGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this ellipse will be displayed.
* @memberof EllipseGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {EllipseGraphics} [result] The object onto which to store the result.
* @returns {EllipseGraphics} The modified result parameter or a new instance if one was not provided.
*/
EllipseGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new EllipseGraphics(this);
}
result.rotation = this.rotation;
result.semiMajorAxis = this.semiMajorAxis;
result.semiMinorAxis = this.semiMinorAxis;
result.show = this.show;
result.material = this.material;
result.height = this.height;
result.extrudedHeight = this.extrudedHeight;
result.granularity = this.granularity;
result.stRotation = this.stRotation;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.numberOfVerticalLines = this.numberOfVerticalLines;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {EllipseGraphics} source The object to be merged into this object.
*/
EllipseGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.rotation = defaultValue(this.rotation, source.rotation);
this.semiMajorAxis = defaultValue(this.semiMajorAxis, source.semiMajorAxis);
this.semiMinorAxis = defaultValue(this.semiMinorAxis, source.semiMinorAxis);
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.height = defaultValue(this.height, source.height);
this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
this.granularity = defaultValue(this.granularity, source.granularity);
this.stRotation = defaultValue(this.stRotation, source.stRotation);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.numberOfVerticalLines = defaultValue(this.numberOfVerticalLines, source.numberOfVerticalLines);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return EllipseGraphics;
});
/*global define*/
define('DataSources/EllipsoidGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describe an ellipsoid or sphere. The center position and orientation are determined by the containing {@link Entity}.
*
* @alias EllipsoidGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.radii] A {@link Cartesian3} Property specifying the radii of the ellipsoid.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the ellipsoid.
* @param {Property} [options.fill=true] A boolean Property specifying whether the ellipsoid is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the ellipsoid.
* @param {Property} [options.outline=false] A boolean Property specifying whether the ellipsoid is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.subdivisions=128] A Property specifying the number of samples per outline ring, determining the granularity of the curvature.
* @param {Property} [options.stackPartitions=64] A Property specifying the number of stacks.
* @param {Property} [options.slicePartitions=64] A Property specifying the number of radial slices.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the ellipsoid casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this ellipsoid will be displayed.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Spheres%20and%20Ellipsoids.html|Cesium Sandcastle Spheres and Ellipsoids Demo}
*/
function EllipsoidGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._radii = undefined;
this._radiiSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._stackPartitions = undefined;
this._stackPartitionsSubscription = undefined;
this._slicePartitions = undefined;
this._slicePartitionsSubscription = undefined;
this._subdivisions = undefined;
this._subdivisionsSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(EllipsoidGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof EllipsoidGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the ellipsoid.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the {@link Cartesian3} {@link Property} specifying the radii of the ellipsoid.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
*/
radii : createPropertyDescriptor('radii'),
/**
* Gets or sets the Property specifying the material used to fill the ellipsoid.
* @memberof EllipsoidGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the boolean Property specifying whether the ellipsoid is filled with the provided material.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the ellipsoid is outlined.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the Property specifying the number of stacks.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default 64
*/
stackPartitions : createPropertyDescriptor('stackPartitions'),
/**
* Gets or sets the Property specifying the number of radial slices.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default 64
*/
slicePartitions : createPropertyDescriptor('slicePartitions'),
/**
* Gets or sets the Property specifying the number of samples per outline ring, determining the granularity of the curvature.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default 128
*/
subdivisions : createPropertyDescriptor('subdivisions'),
/**
* Get or sets the enum Property specifying whether the ellipsoid
* casts or receives shadows from each light source.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this ellipsoid will be displayed.
* @memberof EllipsoidGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {EllipsoidGraphics} [result] The object onto which to store the result.
* @returns {EllipsoidGraphics} The modified result parameter or a new instance if one was not provided.
*/
EllipsoidGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new EllipsoidGraphics(this);
}
result.show = this.show;
result.radii = this.radii;
result.material = this.material;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.stackPartitions = this.stackPartitions;
result.slicePartitions = this.slicePartitions;
result.subdivisions = this.subdivisions;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {EllipsoidGraphics} source The object to be merged into this object.
*/
EllipsoidGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.radii = defaultValue(this.radii, source.radii);
this.material = defaultValue(this.material, source.material);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.stackPartitions = defaultValue(this.stackPartitions, source.stackPartitions);
this.slicePartitions = defaultValue(this.slicePartitions, source.slicePartitions);
this.subdivisions = defaultValue(this.subdivisions, source.subdivisions);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return EllipsoidGraphics;
});
/*global define*/
define('DataSources/LabelGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createPropertyDescriptor) {
'use strict';
/**
* Describes a two dimensional label located at the position of the containing {@link Entity}.
*
*
*
* Example labels
*
*
*
* @alias LabelGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.text] A Property specifying the text. Explicit newlines '\n' are supported.
* @param {Property} [options.font='10px sans-serif'] A Property specifying the CSS font.
* @param {Property} [options.style=LabelStyle.FILL] A Property specifying the {@link LabelStyle}.
* @param {Property} [options.fillColor=Color.WHITE] A Property specifying the fill {@link Color}.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the outline {@link Color}.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the outline width.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the label.
* @param {Property} [options.showBackground=false] A boolean Property specifying the visibility of the background behind the label.
* @param {Property} [options.backgroundColor=new Color(0.165, 0.165, 0.165, 0.8)] A Property specifying the background {@link Color}.
* @param {Property} [options.backgroundPadding=new Cartesian2(7, 5)] A {@link Cartesian2} Property specifying the horizontal and vertical background padding in pixels.
* @param {Property} [options.scale=1.0] A numeric Property specifying the scale to apply to the text.
* @param {Property} [options.horizontalOrigin=HorizontalOrigin.CENTER] A Property specifying the {@link HorizontalOrigin}.
* @param {Property} [options.verticalOrigin=VerticalOrigin.CENTER] A Property specifying the {@link VerticalOrigin}.
* @param {Property} [options.eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the eye offset.
* @param {Property} [options.pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Property specifying the pixel offset.
* @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera.
* @param {Property} [options.pixelOffsetScaleByDistance] A {@link NearFarScalar} Property used to set pixelOffset based on distance from the camera.
* @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
*/
function LabelGraphics(options) {
this._text = undefined;
this._textSubscription = undefined;
this._font = undefined;
this._fontSubscription = undefined;
this._style = undefined;
this._styleSubscription = undefined;
this._fillColor = undefined;
this._fillColorSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._horizontalOrigin = undefined;
this._horizontalOriginSubscription = undefined;
this._verticalOrigin = undefined;
this._verticalOriginSubscription = undefined;
this._eyeOffset = undefined;
this._eyeOffsetSubscription = undefined;
this._heightReference = undefined;
this._heightReferenceSubscription = undefined;
this._pixelOffset = undefined;
this._pixelOffsetSubscription = undefined;
this._scale = undefined;
this._scaleSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._showBackground = undefined;
this._showBackgroundSubscription = undefined;
this._backgroundColor = undefined;
this._backgroundColorSubscription = undefined;
this._backgroundPadding = undefined;
this._backgroundPaddingSubscription = undefined;
this._translucencyByDistance = undefined;
this._translucencyByDistanceSubscription = undefined;
this._pixelOffsetScaleByDistance = undefined;
this._pixelOffsetScaleByDistanceSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(LabelGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof LabelGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the string Property specifying the text of the label.
* Explicit newlines '\n' are supported.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
text : createPropertyDescriptor('text'),
/**
* Gets or sets the string Property specifying the font in CSS syntax.
* @memberof LabelGraphics.prototype
* @type {Property}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/font|CSS font on MDN}
*/
font : createPropertyDescriptor('font'),
/**
* Gets or sets the Property specifying the {@link LabelStyle}.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
style : createPropertyDescriptor('style'),
/**
* Gets or sets the Property specifying the fill {@link Color}.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
fillColor : createPropertyDescriptor('fillColor'),
/**
* Gets or sets the Property specifying the outline {@link Color}.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the outline width.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the Property specifying the {@link HorizontalOrigin}.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
horizontalOrigin : createPropertyDescriptor('horizontalOrigin'),
/**
* Gets or sets the Property specifying the {@link VerticalOrigin}.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
verticalOrigin : createPropertyDescriptor('verticalOrigin'),
/**
* Gets or sets the {@link Cartesian3} Property specifying the label's offset in eye coordinates.
* Eye coordinates is a left-handed coordinate system, where x
points towards the viewer's
* right, y
points up, and z
points into the screen.
*
* An eye offset is commonly used to arrange multiple labels or objects at the same position, e.g., to
* arrange a label above its corresponding 3D model.
*
* Below, the label is positioned at the center of the Earth but an eye offset makes it always
* appear on top of the Earth regardless of the viewer's or Earth's orientation.
*
*
*
*
*
*
*
l.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);
*
*
* @memberof LabelGraphics.prototype
* @type {Property}
* @default Cartesian3.ZERO
*/
eyeOffset : createPropertyDescriptor('eyeOffset'),
/**
* Gets or sets the Property specifying the {@link HeightReference}.
* @memberof LabelGraphics.prototype
* @type {Property}
* @default HeightReference.NONE
*/
heightReference : createPropertyDescriptor('heightReference'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the label's pixel offset in screen space
* from the origin of this label. This is commonly used to align multiple labels and labels at
* the same position, e.g., an image and text. The screen space origin is the top, left corner of the
* canvas; x
increases from left to right, and y
increases from top to bottom.
*
*
*
* default
* l.pixeloffset = new Cartesian2(25, 75);
*
* The label's origin is indicated by the yellow point.
*
*
* @memberof LabelGraphics.prototype
* @type {Property}
* @default Cartesian2.ZERO
*/
pixelOffset : createPropertyDescriptor('pixelOffset'),
/**
* Gets or sets the numeric Property specifying the uniform scale to apply to the image.
* A scale greater than 1.0
enlarges the label while a scale less than 1.0
shrinks it.
*
*
*
* From left to right in the above image, the scales are
0.5
,
1.0
,
* and
2.0
.
*
*
* @memberof LabelGraphics.prototype
* @type {Property}
* @default 1.0
*/
scale : createPropertyDescriptor('scale'),
/**
* Gets or sets the boolean Property specifying the visibility of the label.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the boolean Property specifying the visibility of the background behind the label.
* @memberof LabelGraphics.prototype
* @type {Property}
* @default false
*/
showBackground : createPropertyDescriptor('showBackground'),
/**
* Gets or sets the Property specifying the background {@link Color}.
* @memberof LabelGraphics.prototype
* @type {Property}
* @default new Color(0.165, 0.165, 0.165, 0.8)
*/
backgroundColor : createPropertyDescriptor('backgroundColor'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the label's horizontal and vertical
* background padding in pixels.
* @memberof LabelGraphics.prototype
* @type {Property}
* @default new Cartesian2(7, 5)
*/
backgroundPadding : createPropertyDescriptor('backgroundPadding'),
/**
* Gets or sets {@link NearFarScalar} Property specifying the translucency of the label based on the distance from the camera.
* A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the label's translucency remains clamped to the nearest bound.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
translucencyByDistance : createPropertyDescriptor('translucencyByDistance'),
/**
* Gets or sets {@link NearFarScalar} Property specifying the pixel offset of the label based on the distance from the camera.
* A label's pixel offset will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the label's pixel offset remains clamped to the nearest bound.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this label will be displayed.
* @memberof LabelGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {LabelGraphics} [result] The object onto which to store the result.
* @returns {LabelGraphics} The modified result parameter or a new instance if one was not provided.
*/
LabelGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new LabelGraphics(this);
}
result.text = this.text;
result.font = this.font;
result.show = this.show;
result.style = this.style;
result.fillColor = this.fillColor;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.showBackground = this.showBackground;
result.backgroundColor = this.backgroundColor;
result.backgroundPadding = this.backgroundPadding;
result.scale = this.scale;
result.horizontalOrigin = this.horizontalOrigin;
result.verticalOrigin = this.verticalOrigin;
result.eyeOffset = this.eyeOffset;
result.heightReference = this.heightReference;
result.pixelOffset = this.pixelOffset;
result.translucencyByDistance = this.translucencyByDistance;
result.pixelOffsetScaleByDistance = this.pixelOffsetScaleByDistance;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {LabelGraphics} source The object to be merged into this object.
*/
LabelGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.text = defaultValue(this.text, source.text);
this.font = defaultValue(this.font, source.font);
this.show = defaultValue(this.show, source.show);
this.style = defaultValue(this.style, source.style);
this.fillColor = defaultValue(this.fillColor, source.fillColor);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.showBackground = defaultValue(this.showBackground, source.showBackground);
this.backgroundColor = defaultValue(this.backgroundColor, source.backgroundColor);
this.backgroundPadding = defaultValue(this.backgroundPadding, source.backgroundPadding);
this.scale = defaultValue(this.scale, source.scale);
this.horizontalOrigin = defaultValue(this.horizontalOrigin, source.horizontalOrigin);
this.verticalOrigin = defaultValue(this.verticalOrigin, source.verticalOrigin);
this.eyeOffset = defaultValue(this.eyeOffset, source.eyeOffset);
this.heightReference = defaultValue(this.heightReference, source.heightReference);
this.pixelOffset = defaultValue(this.pixelOffset, source.pixelOffset);
this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance);
this.pixelOffsetScaleByDistance = defaultValue(this._pixelOffsetScaleByDistance, source.pixelOffsetScaleByDistance);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return LabelGraphics;
});
/*global define*/
define('DataSources/NodeTransformationProperty',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'../Core/TranslationRotationScale',
'./createPropertyDescriptor',
'./Property'
], function(
defaultValue,
defined,
defineProperties,
Event,
TranslationRotationScale,
createPropertyDescriptor,
Property) {
'use strict';
var defaultNodeTransformation = new TranslationRotationScale();
/**
* A {@link Property} that produces {@link TranslationRotationScale} data.
* @alias NodeTransformationProperty
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.translation=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the (x, y, z) translation to apply to the node.
* @param {Property} [options.rotation=Quaternion.IDENTITY] A {@link Quaternion} Property specifying the (x, y, z, w) rotation to apply to the node.
* @param {Property} [options.scale=new Cartesian3(1.0, 1.0, 1.0)] A {@link Cartesian3} Property specifying the (x, y, z) scaling to apply to the node.
*/
var NodeTransformationProperty = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._translation = undefined;
this._translationSubscription = undefined;
this._rotation = undefined;
this._rotationSubscription = undefined;
this._scale = undefined;
this._scaleSubscription = undefined;
this.translation = options.translation;
this.rotation = options.rotation;
this.scale = options.scale;
};
defineProperties(NodeTransformationProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof NodeTransformationProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._translation) &&
Property.isConstant(this._rotation) &&
Property.isConstant(this._scale);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof NodeTransformationProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the {@link Cartesian3} Property specifying the (x, y, z) translation to apply to the node.
* @memberof NodeTransformationProperty.prototype
* @type {Property}
* @default Cartesian3.ZERO
*/
translation : createPropertyDescriptor('translation'),
/**
* Gets or sets the {@link Quaternion} Property specifying the (x, y, z, w) rotation to apply to the node.
* @memberof NodeTransformationProperty.prototype
* @type {Property}
* @default Quaternion.IDENTITY
*/
rotation : createPropertyDescriptor('rotation'),
/**
* Gets or sets the {@link Cartesian3} Property specifying the (x, y, z) scaling to apply to the node.
* @memberof NodeTransformationProperty.prototype
* @type {Property}
* @default new Cartesian3(1.0, 1.0, 1.0)
*/
scale : createPropertyDescriptor('scale')
});
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {TranslationRotationScale} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {TranslationRotationScale} The modified result parameter or a new instance if the result parameter was not supplied.
*/
NodeTransformationProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = new TranslationRotationScale();
}
result.translation = Property.getValueOrClonedDefault(this._translation, time, defaultNodeTransformation.translation, result.translation);
result.rotation = Property.getValueOrClonedDefault(this._rotation, time, defaultNodeTransformation.rotation, result.rotation);
result.scale = Property.getValueOrClonedDefault(this._scale, time, defaultNodeTransformation.scale, result.scale);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
NodeTransformationProperty.prototype.equals = function(other) {
return this === other ||
(other instanceof NodeTransformationProperty &&
Property.equals(this._translation, other._translation) &&
Property.equals(this._rotation, other._rotation) &&
Property.equals(this._scale, other._scale));
};
return NodeTransformationProperty;
});
/*global define*/
define('DataSources/PropertyBag',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./ConstantProperty',
'./createPropertyDescriptor',
'./Property'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
ConstantProperty,
createPropertyDescriptor,
Property) {
'use strict';
/**
* A {@link Property} whose value is a key-value mapping of property names to the computed value of other properties.
*
* @alias PropertyBag
* @constructor
*
* @param {Object} [value] An object, containing key-value mapping of property names to properties.
* @param {Function} [createPropertyCallback] A function that will be called when the value of any of the properties in value are not a Property.
*/
var PropertyBag = function(value, createPropertyCallback) {
this._propertyNames = [];
this._definitionChanged = new Event();
if (defined(value)) {
this.merge(value, createPropertyCallback);
}
};
defineProperties(PropertyBag.prototype, {
/**
* Gets the names of all properties registered on this instance.
* @memberof PropertyBag.prototype
* @type {Array}
*/
propertyNames : {
get : function() {
return this._propertyNames;
}
},
/**
* Gets a value indicating if this property is constant. This property
* is considered constant if all property items in this object are constant.
* @memberof PropertyBag.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
var propertyNames = this._propertyNames;
for (var i = 0, len = propertyNames.length; i < len; i++) {
if (!Property.isConstant(this[propertyNames[i]])) {
return false;
}
}
return true;
}
},
/**
* Gets the event that is raised whenever the set of properties contained in this
* object changes, or one of the properties itself changes.
*
* @memberof PropertyBag.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
}
});
/**
* Determines if this object has defined a property with the given name.
*
* @param {String} propertyName The name of the property to check for.
*
* @returns {Boolean} True if this object has defined a property with the given name, false otherwise.
*/
PropertyBag.prototype.hasProperty = function(propertyName) {
return this._propertyNames.indexOf(propertyName) !== -1;
};
function createConstantProperty(value) {
return new ConstantProperty(value);
}
/**
* Adds a property to this object.
*
* @param {String} propertyName The name of the property to add.
* @param {Any} [value] The value of the new property, if provided.
* @param {Function} [createPropertyCallback] A function that will be called when the value of this new property is set to a value that is not a Property.
*
* @exception {DeveloperError} "propertyName" is already a registered property.
*/
PropertyBag.prototype.addProperty = function(propertyName, value, createPropertyCallback) {
var propertyNames = this._propertyNames;
if (!defined(propertyName)) {
throw new DeveloperError('propertyName is required.');
}
if (propertyNames.indexOf(propertyName) !== -1) {
throw new DeveloperError(propertyName + ' is already a registered property.');
}
propertyNames.push(propertyName);
Object.defineProperty(this, propertyName, createPropertyDescriptor(propertyName, true, defaultValue(createPropertyCallback, createConstantProperty)));
if (defined(value)) {
this[propertyName] = value;
}
this._definitionChanged.raiseEvent(this);
};
/**
* Removed a property previously added with addProperty.
*
* @param {String} propertyName The name of the property to remove.
*
* @exception {DeveloperError} "propertyName" is not a registered property.
*/
PropertyBag.prototype.removeProperty = function(propertyName) {
var propertyNames = this._propertyNames;
var index = propertyNames.indexOf(propertyName);
if (!defined(propertyName)) {
throw new DeveloperError('propertyName is required.');
}
if (index === -1) {
throw new DeveloperError(propertyName + ' is not a registered property.');
}
this._propertyNames.splice(index, 1);
delete this[propertyName];
this._definitionChanged.raiseEvent(this);
};
/**
* Gets the value of this property. Each contained property will be evaluated at the given time, and the overall
* result will be an object, mapping property names to those values.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* Note that any properties in result which are not part of this PropertyBag will be left as-is.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PropertyBag.prototype.getValue = function(time, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(result)) {
result = {};
}
var propertyNames = this._propertyNames;
for (var i = 0, len = propertyNames.length; i < len; i++) {
var propertyName = propertyNames[i];
result[propertyName] = Property.getValueOrUndefined(this[propertyName], time, result[propertyName]);
}
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {Object} source The object to be merged into this object.
* @param {Function} [createPropertyCallback] A function that will be called when the value of any of the properties in value are not a Property.
*/
PropertyBag.prototype.merge = function(source, createPropertyCallback) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
var propertyNames = this._propertyNames;
var sourcePropertyNames = defined(source._propertyNames) ? source._propertyNames : Object.keys(source);
for (var i = 0, len = sourcePropertyNames.length; i < len; i++) {
var name = sourcePropertyNames[i];
var targetProperty = this[name];
var sourceProperty = source[name];
//Custom properties that are registered on the source must also be added to this.
if (!defined(targetProperty) && propertyNames.indexOf(name) === -1) {
this.addProperty(name, undefined, createPropertyCallback);
}
if (defined(sourceProperty)) {
if (defined(targetProperty)) {
if (defined(targetProperty.merge)) {
targetProperty.merge(sourceProperty);
}
} else if (defined(sourceProperty.merge) && defined(sourceProperty.clone)) {
this[name] = sourceProperty.clone();
} else {
this[name] = sourceProperty;
}
}
}
};
function propertiesEqual(a, b) {
var aPropertyNames = a._propertyNames;
var bPropertyNames = b._propertyNames;
var len = aPropertyNames.length;
if (len !== bPropertyNames.length) {
return false;
}
for (var aIndex = 0; aIndex < len; ++aIndex) {
var name = aPropertyNames[aIndex];
var bIndex = bPropertyNames.indexOf(name);
if (bIndex === -1) {
return false;
}
if (!Property.equals(a[name], b[name])) {
return false;
}
}
return true;
}
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PropertyBag.prototype.equals = function(other) {
return this === other || //
(other instanceof PropertyBag && //
propertiesEqual(this, other));
};
return PropertyBag;
});
/*global define*/
define('DataSources/ModelGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createPropertyDescriptor',
'./NodeTransformationProperty',
'./PropertyBag'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createPropertyDescriptor,
NodeTransformationProperty,
PropertyBag) {
'use strict';
function createNodeTransformationProperty(value) {
return new NodeTransformationProperty(value);
}
function createNodeTransformationPropertyBag(value) {
return new PropertyBag(value, createNodeTransformationProperty);
}
/**
* A 3D model based on {@link https://github.com/KhronosGroup/glTF|glTF}, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
* The position and orientation of the model is determined by the containing {@link Entity}.
*
* Cesium includes support for glTF geometry, materials, animations, and skinning.
* Cameras and lights are not currently supported.
*
*
* @alias ModelGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.uri] A string Property specifying the URI of the glTF asset.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the model.
* @param {Property} [options.scale=1.0] A numeric Property specifying a uniform linear scale.
* @param {Property} [options.minimumPixelSize=0.0] A numeric Property specifying the approximate minimum pixel size of the model regardless of zoom.
* @param {Property} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
* @param {Property} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
* @param {Property} [options.runAnimations=true] A boolean Property specifying if glTF animations specified in the model should be started.
* @param {Property} [options.nodeTransformations] An object, where keys are names of nodes, and values are {@link TranslationRotationScale} Properties describing the transformation to apply to that node.
* @param {Property} [options.shadows=ShadowMode.ENABLED] An enum Property specifying whether the model casts or receives shadows from each light source.
* @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this model will be displayed.
* @param {Property} [options.silhouetteColor=Color.RED] A Property specifying the {@link Color} of the silhouette.
* @param {Property} [options.silhouetteSize=0.0] A numeric Property specifying the size of the silhouette in pixels.
* @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} that blends with the model's rendered color.
* @param {Property} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] An enum Property specifying how the color blends with the model.
* @param {Property} [options.colorBlendAmount=0.5] A numeric Property specifying the color strength when the colorBlendMode
is MIX
. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
*
* @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo}
*/
function ModelGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._scale = undefined;
this._scaleSubscription = undefined;
this._minimumPixelSize = undefined;
this._minimumPixelSizeSubscription = undefined;
this._maximumScale = undefined;
this._maximumScaleSubscription = undefined;
this._incrementallyLoadTextures = undefined;
this._incrementallyLoadTexturesSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._uri = undefined;
this._uriSubscription = undefined;
this._runAnimations = undefined;
this._runAnimationsSubscription = undefined;
this._nodeTransformations = undefined;
this._nodeTransformationsSubscription = undefined;
this._heightReference = undefined;
this._heightReferenceSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._silhouetteColor = undefined;
this._silhouetteColorSubscription = undefined;
this._silhouetteSize = undefined;
this._silhouetteSizeSubscription = undefined;
this._color = undefined;
this._colorSubscription = undefined;
this._colorBlendMode = undefined;
this._colorBlendModeSubscription = undefined;
this._colorBlendAmount = undefined;
this._colorBlendAmountSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(ModelGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof ModelGraphics.prototype
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the model.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the numeric Property specifying a uniform linear scale
* for this model. Values greater than 1.0 increase the size of the model while
* values less than 1.0 decrease it.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default 1.0
*/
scale : createPropertyDescriptor('scale'),
/**
* Gets or sets the numeric Property specifying the approximate minimum
* pixel size of the model regardless of zoom. This can be used to ensure that
* a model is visible even when the viewer zooms out. When 0.0
,
* no minimum size is enforced.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default 0.0
*/
minimumPixelSize : createPropertyDescriptor('minimumPixelSize'),
/**
* Gets or sets the numeric Property specifying the maximum scale
* size of a model. This property is used as an upper limit for
* {@link ModelGraphics#minimumPixelSize}.
* @memberof ModelGraphics.prototype
* @type {Property}
*/
maximumScale : createPropertyDescriptor('maximumScale'),
/**
* Get or sets the boolean Property specifying whether textures
* may continue to stream in after the model is loaded.
* @memberof ModelGraphics.prototype
* @type {Property}
*/
incrementallyLoadTextures : createPropertyDescriptor('incrementallyLoadTextures'),
/**
* Get or sets the enum Property specifying whether the model
* casts or receives shadows from each light source.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default ShadowMode.ENABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the string Property specifying the URI of the glTF asset.
* @memberof ModelGraphics.prototype
* @type {Property}
*/
uri : createPropertyDescriptor('uri'),
/**
* Gets or sets the boolean Property specifying if glTF animations should be run.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default true
*/
runAnimations : createPropertyDescriptor('runAnimations'),
/**
* Gets or sets the set of node transformations to apply to this model. This is represented as an {@link PropertyBag}, where keys are
* names of nodes, and values are {@link TranslationRotationScale} Properties describing the transformation to apply to that node.
* @memberof ModelGraphics.prototype
* @type {PropertyBag}
*/
nodeTransformations : createPropertyDescriptor('nodeTransformations', undefined, createNodeTransformationPropertyBag),
/**
* Gets or sets the Property specifying the {@link HeightReference}.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default HeightReference.NONE
*/
heightReference : createPropertyDescriptor('heightReference'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this model will be displayed.
* @memberof ModelGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition'),
/**
* Gets or sets the Property specifying the {@link Color} of the silhouette.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default Color.RED
*/
silhouetteColor: createPropertyDescriptor('silhouetteColor'),
/**
* Gets or sets the numeric Property specifying the size of the silhouette in pixels.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default 0.0
*/
silhouetteSize : createPropertyDescriptor('silhouetteSize'),
/**
* Gets or sets the Property specifying the {@link Color} that blends with the model's rendered color.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the enum Property specifying how the color blends with the model.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default ColorBlendMode.HIGHLIGHT
*/
colorBlendMode : createPropertyDescriptor('colorBlendMode'),
/**
* A numeric Property specifying the color strength when the colorBlendMode
is MIX.
* A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with
* any value in-between resulting in a mix of the two.
* @memberof ModelGraphics.prototype
* @type {Property}
* @default 0.5
*/
colorBlendAmount : createPropertyDescriptor('colorBlendAmount')
});
/**
* Duplicates this instance.
*
* @param {ModelGraphics} [result] The object onto which to store the result.
* @returns {ModelGraphics} The modified result parameter or a new instance if one was not provided.
*/
ModelGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new ModelGraphics(this);
}
result.show = this.show;
result.scale = this.scale;
result.minimumPixelSize = this.minimumPixelSize;
result.maximumScale = this.maximumScale;
result.incrementallyLoadTextures = this.incrementallyLoadTextures;
result.shadows = this.shadows;
result.uri = this.uri;
result.runAnimations = this.runAnimations;
result.nodeTransformations = this.nodeTransformations;
result.heightReference = this._heightReference;
result.distanceDisplayCondition = this.distanceDisplayCondition;
result.silhouetteColor = this.silhouetteColor;
result.silhouetteSize = this.silhouetteSize;
result.color = this.color;
result.colorBlendMode = this.colorBlendMode;
result.colorBlendAmount = this.colorBlendAmount;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {ModelGraphics} source The object to be merged into this object.
*/
ModelGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.scale = defaultValue(this.scale, source.scale);
this.minimumPixelSize = defaultValue(this.minimumPixelSize, source.minimumPixelSize);
this.maximumScale = defaultValue(this.maximumScale, source.maximumScale);
this.incrementallyLoadTextures = defaultValue(this.incrementallyLoadTextures, source.incrementallyLoadTextures);
this.shadows = defaultValue(this.shadows, source.shadows);
this.uri = defaultValue(this.uri, source.uri);
this.runAnimations = defaultValue(this.runAnimations, source.runAnimations);
this.heightReference = defaultValue(this.heightReference, source.heightReference);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
this.silhouetteColor = defaultValue(this.silhouetteColor, source.silhouetteColor);
this.silhouetteSize = defaultValue(this.silhouetteSize, source.silhouetteSize);
this.color = defaultValue(this.color, source.color);
this.colorBlendMode = defaultValue(this.colorBlendMode, source.colorBlendMode);
this.colorBlendAmount = defaultValue(this.colorBlendAmount, source.colorBlendAmount);
var sourceNodeTransformations = source.nodeTransformations;
if (defined(sourceNodeTransformations)) {
var targetNodeTransformations = this.nodeTransformations;
if (defined(targetNodeTransformations)) {
targetNodeTransformations.merge(sourceNodeTransformations);
} else {
this.nodeTransformations = new PropertyBag(sourceNodeTransformations, createNodeTransformationProperty);
}
}
};
return ModelGraphics;
});
/*global define*/
define('DataSources/PathGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a polyline defined as the path made by an {@link Entity} as it moves over time.
*
* @alias PathGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.leadTime] A Property specifying the number of seconds behind the object to show.
* @param {Property} [options.trailTime] A Property specifying the number of seconds in front of the object to show.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the path.
* @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the path.
* @param {Property} [options.resolution=60] A numeric Property specifying the maximum number of seconds to step when sampling the position.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this path will be displayed.
*/
function PathGraphics(options) {
this._material = undefined;
this._materialSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._width = undefined;
this._widthSubscription = undefined;
this._resolution = undefined;
this._resolutionSubscription = undefined;
this._leadTime = undefined;
this._leadTimeSubscription = undefined;
this._trailTime = undefined;
this._trailTimeSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(PathGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof PathGraphics.prototype
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the path.
* @memberof PathGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to draw the path.
* @memberof PathGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the numeric Property specifying the width in pixels.
* @memberof PathGraphics.prototype
* @type {Property}
* @default 1.0
*/
width : createPropertyDescriptor('width'),
/**
* Gets or sets the Property specifying the maximum number of seconds to step when sampling the position.
* @memberof PathGraphics.prototype
* @type {Property}
* @default 60
*/
resolution : createPropertyDescriptor('resolution'),
/**
* Gets or sets the Property specifying the number of seconds in front of the object to show.
* @memberof PathGraphics.prototype
* @type {Property}
*/
leadTime : createPropertyDescriptor('leadTime'),
/**
* Gets or sets the Property specifying the number of seconds behind the object to show.
* @memberof PathGraphics.prototype
* @type {Property}
*/
trailTime : createPropertyDescriptor('trailTime'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this path will be displayed.
* @memberof PathGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {PathGraphics} [result] The object onto which to store the result.
* @returns {PathGraphics} The modified result parameter or a new instance if one was not provided.
*/
PathGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new PathGraphics(this);
}
result.material = this.material;
result.width = this.width;
result.resolution = this.resolution;
result.show = this.show;
result.leadTime = this.leadTime;
result.trailTime = this.trailTime;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {PathGraphics} source The object to be merged into this object.
*/
PathGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.material = defaultValue(this.material, source.material);
this.width = defaultValue(this.width, source.width);
this.resolution = defaultValue(this.resolution, source.resolution);
this.show = defaultValue(this.show, source.show);
this.leadTime = defaultValue(this.leadTime, source.leadTime);
this.trailTime = defaultValue(this.trailTime, source.trailTime);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return PathGraphics;
});
/*global define*/
define('DataSources/PointGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createPropertyDescriptor) {
'use strict';
/**
* Describes a graphical point located at the position of the containing {@link Entity}.
*
* @alias PointGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the point.
* @param {Property} [options.pixelSize=1] A numeric Property specifying the size in pixels.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=0] A numeric Property specifying the the outline width in pixels.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the point.
* @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to scale the point based on distance.
* @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera.
* @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this point will be displayed.
*/
function PointGraphics(options) {
this._color = undefined;
this._colorSubscription = undefined;
this._pixelSize = undefined;
this._pixelSizeSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._show = undefined;
this._showSubscription = undefined;
this._scaleByDistance = undefined;
this._scaleByDistanceSubscription = undefined;
this._translucencyByDistance = undefined;
this._translucencyByDistanceSubscription = undefined;
this._heightReference = undefined;
this._heightReferenceSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(PointGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof PointGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the {@link Color} of the point.
* @memberof PointGraphics.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the numeric Property specifying the size in pixels.
* @memberof PointGraphics.prototype
* @type {Property}
* @default 1
*/
pixelSize : createPropertyDescriptor('pixelSize'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof PointGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the the outline width in pixels.
* @memberof PointGraphics.prototype
* @type {Property}
* @default 0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the boolean Property specifying the visibility of the point.
* @memberof PointGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the {@link NearFarScalar} Property used to scale the point based on distance.
* If undefined, a constant size is used.
* @memberof PointGraphics.prototype
* @type {Property}
*/
scaleByDistance : createPropertyDescriptor('scaleByDistance'),
/**
* Gets or sets {@link NearFarScalar} Property specifying the translucency of the point based on the distance from the camera.
* A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the points's translucency remains clamped to the nearest bound.
* @memberof PointGraphics.prototype
* @type {Property}
*/
translucencyByDistance : createPropertyDescriptor('translucencyByDistance'),
/**
* Gets or sets the Property specifying the {@link HeightReference}.
* @memberof PointGraphics.prototype
* @type {Property}
* @default HeightReference.NONE
*/
heightReference : createPropertyDescriptor('heightReference'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this point will be displayed.
* @memberof PointGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {PointGraphics} [result] The object onto which to store the result.
* @returns {PointGraphics} The modified result parameter or a new instance if one was not provided.
*/
PointGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new PointGraphics(this);
}
result.color = this.color;
result.pixelSize = this.pixelSize;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.show = this.show;
result.scaleByDistance = this.scaleByDistance;
result.translucencyByDistance = this._translucencyByDistance;
result.heightReference = this.heightReference;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {PointGraphics} source The object to be merged into this object.
*/
PointGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.color = defaultValue(this.color, source.color);
this.pixelSize = defaultValue(this.pixelSize, source.pixelSize);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.show = defaultValue(this.show, source.show);
this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance);
this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance);
this.heightReference = defaultValue(this.heightReference, source.heightReference);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return PointGraphics;
});
/*global define*/
define('DataSources/PolygonGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a polygon defined by an hierarchy of linear rings which make up the outer shape and any nested holes.
* The polygon conforms to the curvature of the globe and can be placed on the surface or
* at altitude and can optionally be extruded into a volume.
*
* @alias PolygonGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.hierarchy] A Property specifying the {@link PolygonHierarchy}.
* @param {Property} [options.height=0] A numeric Property specifying the altitude of the polygon relative to the ellipsoid surface.
* @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the polygon's extruded face relative to the ellipsoid surface.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the polygon.
* @param {Property} [options.fill=true] A boolean Property specifying whether the polygon is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the polygon.
* @param {Property} [options.outline=false] A boolean Property specifying whether the polygon is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the polygon texture counter-clockwise from north.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point.
* @param {Property} [options.perPositionHeight=false] A boolean specifying whether or not the the height of each position is used.
* @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
* @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polygon casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polygon will be displayed.
*
* @see Entity
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon Demo}
*/
function PolygonGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._hierarchy = undefined;
this._hierarchySubscription = undefined;
this._height = undefined;
this._heightSubscription = undefined;
this._extrudedHeight = undefined;
this._extrudedHeightSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._stRotation = undefined;
this._stRotationSubscription = undefined;
this._perPositionHeight = undefined;
this._perPositionHeightSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._definitionChanged = new Event();
this._closeTop = undefined;
this._closeTopSubscription = undefined;
this._closeBottom = undefined;
this._closeBottomSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(PolygonGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof PolygonGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the polygon.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to fill the polygon.
* @memberof PolygonGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the Property specifying the {@link PolygonHierarchy}.
* @memberof PolygonGraphics.prototype
* @type {Property}
*/
hierarchy : createPropertyDescriptor('hierarchy'),
/**
* Gets or sets the numeric Property specifying the constant altitude of the polygon.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default 0.0
*/
height : createPropertyDescriptor('height'),
/**
* Gets or sets the numeric Property specifying the altitude of the polygon extrusion.
* If {@link PolygonGraphics#perPositionHeight} is false, the volume starts at {@link PolygonGraphics#height} and ends at this altitude.
* If {@link PolygonGraphics#perPositionHeight} is true, the volume starts at the height of each {@link PolygonGraphics#hierarchy} position and ends at this altitude.
* @memberof PolygonGraphics.prototype
* @type {Property}
*/
extrudedHeight : createPropertyDescriptor('extrudedHeight'),
/**
* Gets or sets the numeric Property specifying the angular distance between points on the polygon.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default {CesiumMath.RADIANS_PER_DEGREE}
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Gets or sets the numeric property specifying the rotation of the polygon texture counter-clockwise from north.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default 0
*/
stRotation : createPropertyDescriptor('stRotation'),
/**
* Gets or sets the boolean Property specifying whether the polygon is filled with the provided material.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the polygon is outlined.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the boolean specifying whether or not the the height of each position is used.
* If true, the shape will have non-uniform altitude defined by the height of each {@link PolygonGraphics#hierarchy} position.
* If false, the shape will have a constant altitude as specified by {@link PolygonGraphics#height}.
* @memberof PolygonGraphics.prototype
* @type {Property}
*/
perPositionHeight : createPropertyDescriptor('perPositionHeight'),
/**
* Gets or sets a boolean specifying whether or not the top of an extruded polygon is included.
* @memberof PolygonGraphics.prototype
* @type {Property}
*/
closeTop : createPropertyDescriptor('closeTop'),
/**
* Gets or sets a boolean specifying whether or not the bottom of an extruded polygon is included.
* @memberof PolygonGraphics.prototype
* @type {Property}
*/
closeBottom : createPropertyDescriptor('closeBottom'),
/**
* Get or sets the enum Property specifying whether the polygon
* casts or receives shadows from each light source.
* @memberof PolygonGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polygon will be displayed.
* @memberof BillboardGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {PolygonGraphics} [result] The object onto which to store the result.
* @returns {PolygonGraphics} The modified result parameter or a new instance if one was not provided.
*/
PolygonGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new PolygonGraphics(this);
}
result.show = this.show;
result.material = this.material;
result.hierarchy = this.hierarchy;
result.height = this.height;
result.extrudedHeight = this.extrudedHeight;
result.granularity = this.granularity;
result.stRotation = this.stRotation;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.perPositionHeight = this.perPositionHeight;
result.closeTop = this.closeTop;
result.closeBottom = this.closeBottom;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {PolygonGraphics} source The object to be merged into this object.
*/
PolygonGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.hierarchy = defaultValue(this.hierarchy, source.hierarchy);
this.height = defaultValue(this.height, source.height);
this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
this.granularity = defaultValue(this.granularity, source.granularity);
this.stRotation = defaultValue(this.stRotation, source.stRotation);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.perPositionHeight = defaultValue(this.perPositionHeight, source.perPositionHeight);
this.closeTop = defaultValue(this.closeTop, source.closeTop);
this.closeBottom = defaultValue(this.closeBottom, source.closeBottom);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return PolygonGraphics;
});
/*global define*/
define('DataSources/PolylineGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a polyline defined as a line strip. The first two positions define a line segment,
* and each additional position defines a line segment from the previous position. The segments
* can be linear connected points or great arcs.
*
* @alias PolylineGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the line strip.
* @param {Property} [options.followSurface=true] A boolean Property specifying whether the line segments should be great arcs or linearly connected.
* @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the polyline.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the polyline.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if followSurface is true.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polyline casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polyline will be displayed.
*
* @see Entity
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline.html|Cesium Sandcastle Polyline Demo}
*/
function PolylineGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._positions = undefined;
this._positionsSubscription = undefined;
this._followSurface = undefined;
this._followSurfaceSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._widthSubscription = undefined;
this._width = undefined;
this._widthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(PolylineGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof PolylineGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the polyline.
* @memberof PolylineGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to draw the polyline.
* @memberof PolylineGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the Property specifying the array of {@link Cartesian3}
* positions that define the line strip.
* @memberof PolylineGraphics.prototype
* @type {Property}
*/
positions : createPropertyDescriptor('positions'),
/**
* Gets or sets the numeric Property specifying the width in pixels.
* @memberof PolylineGraphics.prototype
* @type {Property}
* @default 1.0
*/
width : createPropertyDescriptor('width'),
/**
* Gets or sets the boolean Property specifying whether the line segments
* should be great arcs or linearly connected.
* @memberof PolylineGraphics.prototype
* @type {Property}
* @default true
*/
followSurface : createPropertyDescriptor('followSurface'),
/**
* Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if followSurface is true.
* @memberof PolylineGraphics.prototype
* @type {Property}
* @default Cesium.Math.RADIANS_PER_DEGREE
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Get or sets the enum Property specifying whether the polyline
* casts or receives shadows from each light source.
* @memberof PolylineGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polyline will be displayed.
* @memberof PolylineGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {PolylineGraphics} [result] The object onto which to store the result.
* @returns {PolylineGraphics} The modified result parameter or a new instance if one was not provided.
*/
PolylineGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new PolylineGraphics(this);
}
result.show = this.show;
result.material = this.material;
result.positions = this.positions;
result.width = this.width;
result.followSurface = this.followSurface;
result.granularity = this.granularity;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {PolylineGraphics} source The object to be merged into this object.
*/
PolylineGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.positions = defaultValue(this.positions, source.positions);
this.width = defaultValue(this.width, source.width);
this.followSurface = defaultValue(this.followSurface, source.followSurface);
this.granularity = defaultValue(this.granularity, source.granularity);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return PolylineGraphics;
});
/*global define*/
define('DataSources/PolylineVolumeGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a polyline volume defined as a line strip and corresponding two dimensional shape which is extruded along it.
* The resulting volume conforms to the curvature of the globe.
*
* @alias PolylineVolumeGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions which define the line strip.
* @param {Property} [options.shape] A Property specifying the array of {@link Cartesian2} positions which define the shape to be extruded.
* @param {Property} [options.cornerType=CornerType.ROUNDED] A {@link CornerType} Property specifying the style of the corners.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the volume.
* @param {Property} [options.fill=true] A boolean Property specifying whether the volume is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the volume.
* @param {Property} [options.outline=false] A boolean Property specifying whether the volume is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the volume casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this volume will be displayed.
*
* @see Entity
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo}
*/
function PolylineVolumeGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._positions = undefined;
this._positionsSubscription = undefined;
this._shape = undefined;
this._shapeSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._cornerType = undefined;
this._cornerTypeSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubsription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(PolylineVolumeGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof PolylineVolumeGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the volume.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to fill the volume.
* @memberof PolylineVolumeGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the Property specifying the array of {@link Cartesian3} positions which define the line strip.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
*/
positions : createPropertyDescriptor('positions'),
/**
* Gets or sets the Property specifying the array of {@link Cartesian2} positions which define the shape to be extruded.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
*/
shape : createPropertyDescriptor('shape'),
/**
* Gets or sets the numeric Property specifying the angular distance between points on the volume.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default {CesiumMath.RADIANS_PER_DEGREE}
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Gets or sets the boolean Property specifying whether the volume is filled with the provided material.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the volume is outlined.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the {@link CornerType} Property specifying the style of the corners.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default CornerType.ROUNDED
*/
cornerType : createPropertyDescriptor('cornerType'),
/**
* Get or sets the enum Property specifying whether the volume
* casts or receives shadows from each light source.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this volume will be displayed.
* @memberof PolylineVolumeGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {PolylineVolumeGraphics} [result] The object onto which to store the result.
* @returns {PolylineVolumeGraphics} The modified result parameter or a new instance if one was not provided.
*/
PolylineVolumeGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new PolylineVolumeGraphics(this);
}
result.show = this.show;
result.material = this.material;
result.positions = this.positions;
result.shape = this.shape;
result.granularity = this.granularity;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.cornerType = this.cornerType;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {PolylineVolumeGraphics} source The object to be merged into this object.
*/
PolylineVolumeGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.positions = defaultValue(this.positions, source.positions);
this.shape = defaultValue(this.shape, source.shape);
this.granularity = defaultValue(this.granularity, source.granularity);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.cornerType = defaultValue(this.cornerType, source.cornerType);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return PolylineVolumeGraphics;
});
/*global define*/
define('DataSources/RectangleGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes graphics for a {@link Rectangle}.
* The rectangle conforms to the curvature of the globe and can be placed on the surface or
* at altitude and can optionally be extruded into a volume.
*
* @alias RectangleGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.coordinates] The Property specifying the {@link Rectangle}.
* @param {Property} [options.height=0] A numeric Property specifying the altitude of the rectangle relative to the ellipsoid surface.
* @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the rectangle's extruded face relative to the ellipsoid surface.
* @param {Property} [options.closeTop=true] A boolean Property specifying whether the rectangle has a top cover when extruded
* @param {Property} [options.closeBottom=true] A boolean Property specifying whether the rectangle has a bottom cover when extruded.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the rectangle.
* @param {Property} [options.fill=true] A boolean Property specifying whether the rectangle is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the rectangle.
* @param {Property} [options.outline=false] A boolean Property specifying whether the rectangle is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.rotation=0.0] A numeric property specifying the rotation of the rectangle clockwise from north.
* @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the rectangle texture counter-clockwise from north.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between points on the rectangle.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the rectangle casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this rectangle will be displayed.
*
* @see Entity
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo}
*/
function RectangleGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._coordinates = undefined;
this._coordinatesSubscription = undefined;
this._height = undefined;
this._heightSubscription = undefined;
this._extrudedHeight = undefined;
this._extrudedHeightSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._stRotation = undefined;
this._stRotationSubscription = undefined;
this._rotation = undefined;
this._rotationSubscription = undefined;
this._closeTop = undefined;
this._closeTopSubscription = undefined;
this._closeBottom = undefined;
this._closeBottomSubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distancedisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(RectangleGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof RectangleGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the rectangle.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the {@link Rectangle}.
* @memberof RectangleGraphics.prototype
* @type {Property}
*/
coordinates : createPropertyDescriptor('coordinates'),
/**
* Gets or sets the Property specifying the material used to fill the rectangle.
* @memberof RectangleGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the numeric Property specifying the altitude of the rectangle.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default 0.0
*/
height : createPropertyDescriptor('height'),
/**
* Gets or sets the numeric Property specifying the altitude of the rectangle extrusion.
* Setting this property creates volume starting at height and ending at this altitude.
* @memberof RectangleGraphics.prototype
* @type {Property}
*/
extrudedHeight : createPropertyDescriptor('extrudedHeight'),
/**
* Gets or sets the numeric Property specifying the angular distance between points on the rectangle.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default {CesiumMath.RADIANS_PER_DEGREE}
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Gets or sets the numeric property specifying the rotation of the rectangle texture counter-clockwise from north.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default 0
*/
stRotation : createPropertyDescriptor('stRotation'),
/**
* Gets or sets the numeric property specifying the rotation of the rectangle clockwise from north.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default 0
*/
rotation : createPropertyDescriptor('rotation'),
/**
* Gets or sets the boolean Property specifying whether the rectangle is filled with the provided material.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the rectangle is outlined.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Gets or sets the boolean Property specifying whether the rectangle has a top cover when extruded.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default true
*/
closeTop : createPropertyDescriptor('closeTop'),
/**
* Gets or sets the boolean Property specifying whether the rectangle has a bottom cover when extruded.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default true
*/
closeBottom : createPropertyDescriptor('closeBottom'),
/**
* Get or sets the enum Property specifying whether the rectangle
* casts or receives shadows from each light source.
* @memberof RectangleGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this rectangle will be displayed.
* @memberof RectangleGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {RectangleGraphics} [result] The object onto which to store the result.
* @returns {RectangleGraphics} The modified result parameter or a new instance if one was not provided.
*/
RectangleGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new RectangleGraphics(this);
}
result.show = this.show;
result.coordinates = this.coordinates;
result.material = this.material;
result.height = this.height;
result.extrudedHeight = this.extrudedHeight;
result.granularity = this.granularity;
result.stRotation = this.stRotation;
result.rotation = this.rotation;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.closeTop = this.closeTop;
result.closeBottom = this.closeBottom;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {RectangleGraphics} source The object to be merged into this object.
*/
RectangleGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.coordinates = defaultValue(this.coordinates, source.coordinates);
this.material = defaultValue(this.material, source.material);
this.height = defaultValue(this.height, source.height);
this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
this.granularity = defaultValue(this.granularity, source.granularity);
this.stRotation = defaultValue(this.stRotation, source.stRotation);
this.rotation = defaultValue(this.rotation, source.rotation);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.closeTop = defaultValue(this.closeTop, source.closeTop);
this.closeBottom = defaultValue(this.closeBottom, source.closeBottom);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return RectangleGraphics;
});
/*global define*/
define('DataSources/WallGraphics',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./createMaterialPropertyDescriptor',
'./createPropertyDescriptor'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
createMaterialPropertyDescriptor,
createPropertyDescriptor) {
'use strict';
/**
* Describes a two dimensional wall defined as a line strip and optional maximum and minimum heights.
* The wall conforms to the curvature of the globe and can be placed along the surface or at altitude.
*
* @alias WallGraphics
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions which define the top of the wall.
* @param {Property} [options.maximumHeights] A Property specifying an array of heights to be used for the top of the wall instead of the height of each position.
* @param {Property} [options.minimumHeights] A Property specifying an array of heights to be used for the bottom of the wall instead of the globe surface.
* @param {Property} [options.show=true] A boolean Property specifying the visibility of the wall.
* @param {Property} [options.fill=true] A boolean Property specifying whether the wall is filled with the provided material.
* @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the wall.
* @param {Property} [options.outline=false] A boolean Property specifying whether the wall is outlined.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
* @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point.
* @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the wall casts or receives shadows from each light source.
* @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this wall will be displayed.
*
* @see Entity
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo}
*/
function WallGraphics(options) {
this._show = undefined;
this._showSubscription = undefined;
this._material = undefined;
this._materialSubscription = undefined;
this._positions = undefined;
this._positionsSubscription = undefined;
this._minimumHeights = undefined;
this._minimumHeightsSubscription = undefined;
this._maximumHeights = undefined;
this._maximumHeightsSubscription = undefined;
this._granularity = undefined;
this._granularitySubscription = undefined;
this._fill = undefined;
this._fillSubscription = undefined;
this._outline = undefined;
this._outlineSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this._shadows = undefined;
this._shadowsSubscription = undefined;
this._distanceDisplayCondition = undefined;
this._distanceDisplayConditionSubscription = undefined;
this._definitionChanged = new Event();
this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
}
defineProperties(WallGraphics.prototype, {
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof WallGraphics.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the boolean Property specifying the visibility of the wall.
* @memberof WallGraphics.prototype
* @type {Property}
* @default true
*/
show : createPropertyDescriptor('show'),
/**
* Gets or sets the Property specifying the material used to fill the wall.
* @memberof WallGraphics.prototype
* @type {MaterialProperty}
* @default Color.WHITE
*/
material : createMaterialPropertyDescriptor('material'),
/**
* Gets or sets the Property specifying the array of {@link Cartesian3} positions which define the top of the wall.
* @memberof WallGraphics.prototype
* @type {Property}
*/
positions : createPropertyDescriptor('positions'),
/**
* Gets or sets the Property specifying an array of heights to be used for the bottom of the wall instead of the surface of the globe.
* If defined, the array must be the same length as {@link Wall#positions}.
* @memberof WallGraphics.prototype
* @type {Property}
*/
minimumHeights : createPropertyDescriptor('minimumHeights'),
/**
* Gets or sets the Property specifying an array of heights to be used for the top of the wall instead of the height of each position.
* If defined, the array must be the same length as {@link Wall#positions}.
* @memberof WallGraphics.prototype
* @type {Property}
*/
maximumHeights : createPropertyDescriptor('maximumHeights'),
/**
* Gets or sets the numeric Property specifying the angular distance between points on the wall.
* @memberof WallGraphics.prototype
* @type {Property}
* @default {CesiumMath.RADIANS_PER_DEGREE}
*/
granularity : createPropertyDescriptor('granularity'),
/**
* Gets or sets the boolean Property specifying whether the wall is filled with the provided material.
* @memberof WallGraphics.prototype
* @type {Property}
* @default true
*/
fill : createPropertyDescriptor('fill'),
/**
* Gets or sets the Property specifying whether the wall is outlined.
* @memberof WallGraphics.prototype
* @type {Property}
* @default false
*/
outline : createPropertyDescriptor('outline'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof WallGraphics.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof WallGraphics.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth'),
/**
* Get or sets the enum Property specifying whether the wall
* casts or receives shadows from each light source.
* @memberof WallGraphics.prototype
* @type {Property}
* @default ShadowMode.DISABLED
*/
shadows : createPropertyDescriptor('shadows'),
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this wall will be displayed.
* @memberof WallGraphics.prototype
* @type {Property}
*/
distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
});
/**
* Duplicates this instance.
*
* @param {WallGraphics} [result] The object onto which to store the result.
* @returns {WallGraphics} The modified result parameter or a new instance if one was not provided.
*/
WallGraphics.prototype.clone = function(result) {
if (!defined(result)) {
return new WallGraphics(this);
}
result.show = this.show;
result.material = this.material;
result.positions = this.positions;
result.minimumHeights = this.minimumHeights;
result.maximumHeights = this.maximumHeights;
result.granularity = this.granularity;
result.fill = this.fill;
result.outline = this.outline;
result.outlineColor = this.outlineColor;
result.outlineWidth = this.outlineWidth;
result.shadows = this.shadows;
result.distanceDisplayCondition = this.distanceDisplayCondition;
return result;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {WallGraphics} source The object to be merged into this object.
*/
WallGraphics.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.show = defaultValue(this.show, source.show);
this.material = defaultValue(this.material, source.material);
this.positions = defaultValue(this.positions, source.positions);
this.minimumHeights = defaultValue(this.minimumHeights, source.minimumHeights);
this.maximumHeights = defaultValue(this.maximumHeights, source.maximumHeights);
this.granularity = defaultValue(this.granularity, source.granularity);
this.fill = defaultValue(this.fill, source.fill);
this.outline = defaultValue(this.outline, source.outline);
this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
this.shadows = defaultValue(this.shadows, source.shadows);
this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
};
return WallGraphics;
});
/*global define*/
define('DataSources/Entity',[
'../Core/Cartesian3',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/Quaternion',
'../Core/Transforms',
'./BillboardGraphics',
'./BoxGraphics',
'./ConstantPositionProperty',
'./CorridorGraphics',
'./createPropertyDescriptor',
'./createRawPropertyDescriptor',
'./CylinderGraphics',
'./EllipseGraphics',
'./EllipsoidGraphics',
'./LabelGraphics',
'./ModelGraphics',
'./PathGraphics',
'./PointGraphics',
'./PolygonGraphics',
'./PolylineGraphics',
'./PolylineVolumeGraphics',
'./Property',
'./RectangleGraphics',
'./WallGraphics'
], function(
Cartesian3,
createGuid,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
Matrix3,
Matrix4,
Quaternion,
Transforms,
BillboardGraphics,
BoxGraphics,
ConstantPositionProperty,
CorridorGraphics,
createPropertyDescriptor,
createRawPropertyDescriptor,
CylinderGraphics,
EllipseGraphics,
EllipsoidGraphics,
LabelGraphics,
ModelGraphics,
PathGraphics,
PointGraphics,
PolygonGraphics,
PolylineGraphics,
PolylineVolumeGraphics,
Property,
RectangleGraphics,
WallGraphics) {
'use strict';
function createConstantPositionProperty(value) {
return new ConstantPositionProperty(value);
}
function createPositionPropertyDescriptor(name) {
return createPropertyDescriptor(name, undefined, createConstantPositionProperty);
}
function createPropertyTypeDescriptor(name, Type) {
return createPropertyDescriptor(name, undefined, function(value) {
if (value instanceof Type) {
return value;
}
return new Type(value);
});
}
/**
* Entity instances aggregate multiple forms of visualization into a single high-level object.
* They can be created manually and added to {@link Viewer#entities} or be produced by
* data sources, such as {@link CzmlDataSource} and {@link GeoJsonDataSource}.
* @alias Entity
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {String} [options.id] A unique identifier for this object. If none is provided, a GUID is generated.
* @param {String} [options.name] A human readable name to display to users. It does not have to be unique.
* @param {TimeIntervalCollection} [options.availability] The availability, if any, associated with this object.
* @param {Boolean} [options.show] A boolean value indicating if the entity and its children are displayed.
* @param {Property} [options.description] A string Property specifying an HTML description for this entity.
* @param {PositionProperty} [options.position] A Property specifying the entity position.
* @param {Property} [options.orientation] A Property specifying the entity orientation.
* @param {Property} [options.viewFrom] A suggested initial offset for viewing this object.
* @param {Entity} [options.parent] A parent entity to associate with this entity.
* @param {BillboardGraphics} [options.billboard] A billboard to associate with this entity.
* @param {BoxGraphics} [options.box] A box to associate with this entity.
* @param {CorridorGraphics} [options.corridor] A corridor to associate with this entity.
* @param {CylinderGraphics} [options.cylinder] A cylinder to associate with this entity.
* @param {EllipseGraphics} [options.ellipse] A ellipse to associate with this entity.
* @param {EllipsoidGraphics} [options.ellipsoid] A ellipsoid to associate with this entity.
* @param {LabelGraphics} [options.label] A options.label to associate with this entity.
* @param {ModelGraphics} [options.model] A model to associate with this entity.
* @param {PathGraphics} [options.path] A path to associate with this entity.
* @param {PointGraphics} [options.point] A point to associate with this entity.
* @param {PolygonGraphics} [options.polygon] A polygon to associate with this entity.
* @param {PolylineGraphics} [options.polyline] A polyline to associate with this entity.
* @param {PolylineVolumeGraphics} [options.polylineVolume] A polylineVolume to associate with this entity.
* @param {RectangleGraphics} [options.rectangle] A rectangle to associate with this entity.
* @param {WallGraphics} [options.wall] A wall to associate with this entity.
*
* @see {@link http://cesiumjs.org/2015/02/02/Visualizing-Spatial-Data/|Visualizing Spatial Data}
*/
function Entity(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var id = options.id;
if (!defined(id)) {
id = createGuid();
}
this._availability = undefined;
this._id = id;
this._definitionChanged = new Event();
this._name = options.name;
this._show = defaultValue(options.show, true);
this._parent = undefined;
this._propertyNames = ['billboard', 'box', 'corridor', 'cylinder', 'description', 'ellipse', //
'ellipsoid', 'label', 'model', 'orientation', 'path', 'point', 'polygon', //
'polyline', 'polylineVolume', 'position', 'rectangle', 'viewFrom', 'wall'];
this._billboard = undefined;
this._billboardSubscription = undefined;
this._box = undefined;
this._boxSubscription = undefined;
this._corridor = undefined;
this._corridorSubscription = undefined;
this._cylinder = undefined;
this._cylinderSubscription = undefined;
this._description = undefined;
this._descriptionSubscription = undefined;
this._ellipse = undefined;
this._ellipseSubscription = undefined;
this._ellipsoid = undefined;
this._ellipsoidSubscription = undefined;
this._label = undefined;
this._labelSubscription = undefined;
this._model = undefined;
this._modelSubscription = undefined;
this._orientation = undefined;
this._orientationSubscription = undefined;
this._path = undefined;
this._pathSubscription = undefined;
this._point = undefined;
this._pointSubscription = undefined;
this._polygon = undefined;
this._polygonSubscription = undefined;
this._polyline = undefined;
this._polylineSubscription = undefined;
this._polylineVolume = undefined;
this._polylineVolumeSubscription = undefined;
this._position = undefined;
this._positionSubscription = undefined;
this._rectangle = undefined;
this._rectangleSubscription = undefined;
this._viewFrom = undefined;
this._viewFromSubscription = undefined;
this._wall = undefined;
this._wallSubscription = undefined;
this._children = [];
/**
* Gets or sets the entity collection that this entity belongs to.
* @type {EntityCollection}
*/
this.entityCollection = undefined;
this.parent = options.parent;
this.merge(options);
}
function updateShow(entity, children, isShowing) {
var length = children.length;
for (var i = 0; i < length; i++) {
var child = children[i];
var childShow = child._show;
var oldValue = !isShowing && childShow;
var newValue = isShowing && childShow;
if (oldValue !== newValue) {
updateShow(child, child._children, isShowing);
}
}
entity._definitionChanged.raiseEvent(entity, 'isShowing', isShowing, !isShowing);
}
defineProperties(Entity.prototype, {
/**
* The availability, if any, associated with this object.
* If availability is undefined, it is assumed that this object's
* other properties will return valid data for any provided time.
* If availability exists, the objects other properties will only
* provide valid data if queried within the given interval.
* @memberof Entity.prototype
* @type {TimeIntervalCollection}
*/
availability : createRawPropertyDescriptor('availability'),
/**
* Gets the unique ID associated with this object.
* @memberof Entity.prototype
* @type {String}
*/
id : {
get : function() {
return this._id;
}
},
/**
* Gets the event that is raised whenever a property or sub-property is changed or modified.
* @memberof Entity.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the name of the object. The name is intended for end-user
* consumption and does not need to be unique.
* @memberof Entity.prototype
* @type {String}
*/
name : createRawPropertyDescriptor('name'),
/**
* Gets or sets whether this entity should be displayed. When set to true,
* the entity is only displayed if the parent entity's show property is also true.
* @memberof Entity.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._show;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (value === this._show) {
return;
}
var wasShowing = this.isShowing;
this._show = value;
var isShowing = this.isShowing;
if (wasShowing !== isShowing) {
updateShow(this, this._children, isShowing);
}
this._definitionChanged.raiseEvent(this, 'show', value, !value);
}
},
/**
* Gets whether this entity is being displayed, taking into account
* the visibility of any ancestor entities.
* @memberof Entity.prototype
* @type {Boolean}
*/
isShowing : {
get : function() {
return this._show && (!defined(this.entityCollection) || this.entityCollection.show) && (!defined(this._parent) || this._parent.isShowing);
}
},
/**
* Gets or sets the parent object.
* @memberof Entity.prototype
* @type {Entity}
*/
parent : {
get : function() {
return this._parent;
},
set : function(value) {
var oldValue = this._parent;
if (oldValue === value) {
return;
}
var wasShowing = this.isShowing;
if (defined(oldValue)) {
var index = oldValue._children.indexOf(this);
oldValue._children.splice(index, 1);
}
this._parent = value;
if (defined(value)) {
value._children.push(this);
}
var isShowing = this.isShowing;
if (wasShowing !== isShowing) {
updateShow(this, this._children, isShowing);
}
this._definitionChanged.raiseEvent(this, 'parent', value, oldValue);
}
},
/**
* Gets the names of all properties registered on this instance.
* @memberof Entity.prototype
* @type {Array}
*/
propertyNames : {
get : function() {
return this._propertyNames;
}
},
/**
* Gets or sets the billboard.
* @memberof Entity.prototype
* @type {BillboardGraphics}
*/
billboard : createPropertyTypeDescriptor('billboard', BillboardGraphics),
/**
* Gets or sets the box.
* @memberof Entity.prototype
* @type {BoxGraphics}
*/
box : createPropertyTypeDescriptor('box', BoxGraphics),
/**
* Gets or sets the corridor.
* @memberof Entity.prototype
* @type {CorridorGraphics}
*/
corridor : createPropertyTypeDescriptor('corridor', CorridorGraphics),
/**
* Gets or sets the cylinder.
* @memberof Entity.prototype
* @type {CylinderGraphics}
*/
cylinder : createPropertyTypeDescriptor('cylinder', CylinderGraphics),
/**
* Gets or sets the description.
* @memberof Entity.prototype
* @type {Property}
*/
description : createPropertyDescriptor('description'),
/**
* Gets or sets the ellipse.
* @memberof Entity.prototype
* @type {EllipseGraphics}
*/
ellipse : createPropertyTypeDescriptor('ellipse', EllipseGraphics),
/**
* Gets or sets the ellipsoid.
* @memberof Entity.prototype
* @type {EllipsoidGraphics}
*/
ellipsoid : createPropertyTypeDescriptor('ellipsoid', EllipsoidGraphics),
/**
* Gets or sets the label.
* @memberof Entity.prototype
* @type {LabelGraphics}
*/
label : createPropertyTypeDescriptor('label', LabelGraphics),
/**
* Gets or sets the model.
* @memberof Entity.prototype
* @type {ModelGraphics}
*/
model : createPropertyTypeDescriptor('model', ModelGraphics),
/**
* Gets or sets the orientation.
* @memberof Entity.prototype
* @type {Property}
*/
orientation : createPropertyDescriptor('orientation'),
/**
* Gets or sets the path.
* @memberof Entity.prototype
* @type {PathGraphics}
*/
path : createPropertyTypeDescriptor('path', PathGraphics),
/**
* Gets or sets the point graphic.
* @memberof Entity.prototype
* @type {PointGraphics}
*/
point : createPropertyTypeDescriptor('point', PointGraphics),
/**
* Gets or sets the polygon.
* @memberof Entity.prototype
* @type {PolygonGraphics}
*/
polygon : createPropertyTypeDescriptor('polygon', PolygonGraphics),
/**
* Gets or sets the polyline.
* @memberof Entity.prototype
* @type {PolylineGraphics}
*/
polyline : createPropertyTypeDescriptor('polyline', PolylineGraphics),
/**
* Gets or sets the polyline volume.
* @memberof Entity.prototype
* @type {PolylineVolumeGraphics}
*/
polylineVolume : createPropertyTypeDescriptor('polylineVolume', PolylineVolumeGraphics),
/**
* Gets or sets the position.
* @memberof Entity.prototype
* @type {PositionProperty}
*/
position : createPositionPropertyDescriptor('position'),
/**
* Gets or sets the rectangle.
* @memberof Entity.prototype
* @type {RectangleGraphics}
*/
rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics),
/**
* Gets or sets the suggested initial offset for viewing this object
* with the camera. The offset is defined in the east-north-up reference frame.
* @memberof Entity.prototype
* @type {Property}
*/
viewFrom : createPropertyDescriptor('viewFrom'),
/**
* Gets or sets the wall.
* @memberof Entity.prototype
* @type {WallGraphics}
*/
wall : createPropertyTypeDescriptor('wall', WallGraphics)
});
/**
* Given a time, returns true if this object should have data during that time.
*
* @param {JulianDate} time The time to check availability for.
* @returns {Boolean} true if the object should have data during the provided time, false otherwise.
*/
Entity.prototype.isAvailable = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var availability = this._availability;
return !defined(availability) || availability.contains(time);
};
/**
* Adds a property to this object. Once a property is added, it can be
* observed with {@link Entity#definitionChanged} and composited
* with {@link CompositeEntityCollection}
*
* @param {String} propertyName The name of the property to add.
*
* @exception {DeveloperError} "propertyName" is a reserved property name.
* @exception {DeveloperError} "propertyName" is already a registered property.
*/
Entity.prototype.addProperty = function(propertyName) {
var propertyNames = this._propertyNames;
if (!defined(propertyName)) {
throw new DeveloperError('propertyName is required.');
}
if (propertyNames.indexOf(propertyName) !== -1) {
throw new DeveloperError(propertyName + ' is already a registered property.');
}
if (propertyName in this) {
throw new DeveloperError(propertyName + ' is a reserved property name.');
}
propertyNames.push(propertyName);
Object.defineProperty(this, propertyName, createRawPropertyDescriptor(propertyName, true));
};
/**
* Removed a property previously added with addProperty.
*
* @param {String} propertyName The name of the property to remove.
*
* @exception {DeveloperError} "propertyName" is a reserved property name.
* @exception {DeveloperError} "propertyName" is not a registered property.
*/
Entity.prototype.removeProperty = function(propertyName) {
var propertyNames = this._propertyNames;
var index = propertyNames.indexOf(propertyName);
if (!defined(propertyName)) {
throw new DeveloperError('propertyName is required.');
}
if (index === -1) {
throw new DeveloperError(propertyName + ' is not a registered property.');
}
this._propertyNames.splice(index, 1);
delete this[propertyName];
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {Entity} source The object to be merged into this object.
*/
Entity.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
//Name, show, and availability are not Property objects and are currently handled differently.
//source.show is intentionally ignored because this.show always has a value.
this.name = defaultValue(this.name, source.name);
this.availability = defaultValue(source.availability, this.availability);
var propertyNames = this._propertyNames;
var sourcePropertyNames = defined(source._propertyNames) ? source._propertyNames : Object.keys(source);
var propertyNamesLength = sourcePropertyNames.length;
for (var i = 0; i < propertyNamesLength; i++) {
var name = sourcePropertyNames[i];
//Ignore parent when merging, this only happens at construction time.
if (name === 'parent') {
continue;
}
var targetProperty = this[name];
var sourceProperty = source[name];
//Custom properties that are registered on the source entity must also
//get registered on this entity.
if (!defined(targetProperty) && propertyNames.indexOf(name) === -1) {
this.addProperty(name);
}
if (defined(sourceProperty)) {
if (defined(targetProperty)) {
if (defined(targetProperty.merge)) {
targetProperty.merge(sourceProperty);
}
} else if (defined(sourceProperty.merge) && defined(sourceProperty.clone)) {
this[name] = sourceProperty.clone();
} else {
this[name] = sourceProperty;
}
}
}
};
var matrix3Scratch = new Matrix3();
var positionScratch = new Cartesian3();
var orientationScratch = new Quaternion();
/**
* @private
*/
Entity.prototype._getModelMatrix = function(time, result) {
var position = Property.getValueOrUndefined(this._position, time, positionScratch);
if (!defined(position)) {
return undefined;
}
var orientation = Property.getValueOrUndefined(this._orientation, time, orientationScratch);
if (!defined(orientation)) {
result = Transforms.eastNorthUpToFixedFrame(position, undefined, result);
} else {
result = Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, result);
}
return result;
};
return Entity;
});
/*global define*/
define('DataSources/EntityCollection',[
'../Core/AssociativeArray',
'../Core/createGuid',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/Iso8601',
'../Core/JulianDate',
'../Core/RuntimeError',
'../Core/TimeInterval',
'./Entity'
], function(
AssociativeArray,
createGuid,
defined,
defineProperties,
DeveloperError,
Event,
Iso8601,
JulianDate,
RuntimeError,
TimeInterval,
Entity) {
'use strict';
var entityOptionsScratch = {
id : undefined
};
function fireChangedEvent(collection) {
if (collection._firing) {
collection._refire = true;
return;
}
if (collection._suspendCount === 0) {
var added = collection._addedEntities;
var removed = collection._removedEntities;
var changed = collection._changedEntities;
if (changed.length !== 0 || added.length !== 0 || removed.length !== 0) {
collection._firing = true;
do {
collection._refire = false;
var addedArray = added.values.slice(0);
var removedArray = removed.values.slice(0);
var changedArray = changed.values.slice(0);
added.removeAll();
removed.removeAll();
changed.removeAll();
collection._collectionChanged.raiseEvent(collection, addedArray, removedArray, changedArray);
} while (collection._refire);
collection._firing = false;
}
}
}
/**
* An observable collection of {@link Entity} instances where each entity has a unique id.
* @alias EntityCollection
* @constructor
*
* @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
*/
function EntityCollection(owner) {
this._owner = owner;
this._entities = new AssociativeArray();
this._addedEntities = new AssociativeArray();
this._removedEntities = new AssociativeArray();
this._changedEntities = new AssociativeArray();
this._suspendCount = 0;
this._collectionChanged = new Event();
this._id = createGuid();
this._show = true;
this._firing = false;
this._refire = false;
}
/**
* Prevents {@link EntityCollection#collectionChanged} events from being raised
* until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
* point a single event will be raised that covers all suspended operations.
* This allows for many items to be added and removed efficiently.
* This function can be safely called multiple times as long as there
* are corresponding calls to {@link EntityCollection#resumeEvents}.
*/
EntityCollection.prototype.suspendEvents = function() {
this._suspendCount++;
};
/**
* Resumes raising {@link EntityCollection#collectionChanged} events immediately
* when an item is added or removed. Any modifications made while while events were suspended
* will be triggered as a single event when this function is called.
* This function is reference counted and can safely be called multiple times as long as there
* are corresponding calls to {@link EntityCollection#resumeEvents}.
*
* @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
*/
EntityCollection.prototype.resumeEvents = function() {
if (this._suspendCount === 0) {
throw new DeveloperError('resumeEvents can not be called before suspendEvents.');
}
this._suspendCount--;
fireChangedEvent(this);
};
/**
* The signature of the event generated by {@link EntityCollection#collectionChanged}.
* @function
*
* @param {EntityCollection} collection The collection that triggered the event.
* @param {Entity[]} added The array of {@link Entity} instances that have been added to the collection.
* @param {Entity[]} removed The array of {@link Entity} instances that have been removed from the collection.
* @param {Entity[]} changed The array of {@link Entity} instances that have been modified.
*/
EntityCollection.collectionChangedEventCallback = undefined;
defineProperties(EntityCollection.prototype, {
/**
* Gets the event that is fired when entities are added or removed from the collection.
* The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
* @memberof EntityCollection.prototype
* @readonly
* @type {Event}
*/
collectionChanged : {
get : function() {
return this._collectionChanged;
}
},
/**
* Gets a globally unique identifier for this collection.
* @memberof EntityCollection.prototype
* @readonly
* @type {String}
*/
id : {
get : function() {
return this._id;
}
},
/**
* Gets the array of Entity instances in the collection.
* This array should not be modified directly.
* @memberof EntityCollection.prototype
* @readonly
* @type {Entity[]}
*/
values : {
get : function() {
return this._entities.values;
}
},
/**
* Gets whether or not this entity collection should be
* displayed. When true, each entity is only displayed if
* its own show property is also true.
* @memberof EntityCollection.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._show;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (value === this._show) {
return;
}
//Since entity.isShowing includes the EntityCollection.show state
//in its calculation, we need to loop over the entities array
//twice, once to get the old showing value and a second time
//to raise the changed event.
this.suspendEvents();
var i;
var oldShows = [];
var entities = this._entities.values;
var entitiesLength = entities.length;
for (i = 0; i < entitiesLength; i++) {
oldShows.push(entities[i].isShowing);
}
this._show = value;
for (i = 0; i < entitiesLength; i++) {
var oldShow = oldShows[i];
var entity = entities[i];
if (oldShow !== entity.isShowing) {
entity.definitionChanged.raiseEvent(entity, 'isShowing', entity.isShowing, oldShow);
}
}
this.resumeEvents();
}
},
/**
* Gets the owner of this entity collection, ie. the data source or composite entity collection which created it.
* @memberof EntityCollection.prototype
* @readonly
* @type {DataSource|CompositeEntityCollection}
*/
owner : {
get : function() {
return this._owner;
}
}
});
/**
* Computes the maximum availability of the entities in the collection.
* If the collection contains a mix of infinitely available data and non-infinite data,
* it will return the interval pertaining to the non-infinite data only. If all
* data is infinite, an infinite interval will be returned.
*
* @returns {TimeInterval} The availability of entities in the collection.
*/
EntityCollection.prototype.computeAvailability = function() {
var startTime = Iso8601.MAXIMUM_VALUE;
var stopTime = Iso8601.MINIMUM_VALUE;
var entities = this._entities.values;
for (var i = 0, len = entities.length; i < len; i++) {
var entity = entities[i];
var availability = entity.availability;
if (defined(availability)) {
var start = availability.start;
var stop = availability.stop;
if (JulianDate.lessThan(start, startTime) && !start.equals(Iso8601.MINIMUM_VALUE)) {
startTime = start;
}
if (JulianDate.greaterThan(stop, stopTime) && !stop.equals(Iso8601.MAXIMUM_VALUE)) {
stopTime = stop;
}
}
}
if (Iso8601.MAXIMUM_VALUE.equals(startTime)) {
startTime = Iso8601.MINIMUM_VALUE;
}
if (Iso8601.MINIMUM_VALUE.equals(stopTime)) {
stopTime = Iso8601.MAXIMUM_VALUE;
}
return new TimeInterval({
start : startTime,
stop : stopTime
});
};
/**
* Add an entity to the collection.
*
* @param {Entity} entity The entity to be added.
* @returns {Entity} The entity that was added.
* @exception {DeveloperError} An entity with already exists in this collection.
*/
EntityCollection.prototype.add = function(entity) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!(entity instanceof Entity)) {
entity = new Entity(entity);
}
var id = entity.id;
var entities = this._entities;
if (entities.contains(id)) {
throw new RuntimeError('An entity with id ' + id + ' already exists in this collection.');
}
entity.entityCollection = this;
entities.set(id, entity);
if (!this._removedEntities.remove(id)) {
this._addedEntities.set(id, entity);
}
entity.definitionChanged.addEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
fireChangedEvent(this);
return entity;
};
/**
* Removes an entity from the collection.
*
* @param {Entity} entity The entity to be removed.
* @returns {Boolean} true if the item was removed, false if it did not exist in the collection.
*/
EntityCollection.prototype.remove = function(entity) {
if (!defined(entity)) {
return false;
}
return this.removeById(entity.id);
};
/**
* Returns true if the provided entity is in this collection, false otherwise.
*
* @param {Entity} entity The entity.
* @returns {Boolean} true if the provided entity is in this collection, false otherwise.
*/
EntityCollection.prototype.contains = function(entity) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
return this._entities.get(entity.id) === entity;
};
/**
* Removes an entity with the provided id from the collection.
*
* @param {Object} id The id of the entity to remove.
* @returns {Boolean} true if the item was removed, false if no item with the provided id existed in the collection.
*/
EntityCollection.prototype.removeById = function(id) {
if (!defined(id)) {
return false;
}
var entities = this._entities;
var entity = entities.get(id);
if (!this._entities.remove(id)) {
return false;
}
if (!this._addedEntities.remove(id)) {
this._removedEntities.set(id, entity);
this._changedEntities.remove(id);
}
this._entities.remove(id);
entity.definitionChanged.removeEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
fireChangedEvent(this);
return true;
};
/**
* Removes all Entities from the collection.
*/
EntityCollection.prototype.removeAll = function() {
//The event should only contain items added before events were suspended
//and the contents of the collection.
var entities = this._entities;
var entitiesLength = entities.length;
var array = entities.values;
var addedEntities = this._addedEntities;
var removed = this._removedEntities;
for (var i = 0; i < entitiesLength; i++) {
var existingItem = array[i];
var existingItemId = existingItem.id;
var addedItem = addedEntities.get(existingItemId);
if (!defined(addedItem)) {
existingItem.definitionChanged.removeEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
removed.set(existingItemId, existingItem);
}
}
entities.removeAll();
addedEntities.removeAll();
this._changedEntities.removeAll();
fireChangedEvent(this);
};
/**
* Gets an entity with the specified id.
*
* @param {Object} id The id of the entity to retrieve.
* @returns {Entity} The entity with the provided id or undefined if the id did not exist in the collection.
*/
EntityCollection.prototype.getById = function(id) {
if (!defined(id)) {
throw new DeveloperError('id is required.');
}
return this._entities.get(id);
};
/**
* Gets an entity with the specified id or creates it and adds it to the collection if it does not exist.
*
* @param {Object} id The id of the entity to retrieve or create.
* @returns {Entity} The new or existing object.
*/
EntityCollection.prototype.getOrCreateEntity = function(id) {
if (!defined(id)) {
throw new DeveloperError('id is required.');
}
var entity = this._entities.get(id);
if (!defined(entity)) {
entityOptionsScratch.id = id;
entity = new Entity(entityOptionsScratch);
this.add(entity);
}
return entity;
};
EntityCollection.prototype._onEntityDefinitionChanged = function(entity) {
var id = entity.id;
if (!this._addedEntities.contains(id)) {
this._changedEntities.set(id, entity);
}
fireChangedEvent(this);
};
return EntityCollection;
});
/*global define*/
define('DataSources/CompositeEntityCollection',[
'../Core/createGuid',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Math',
'./Entity',
'./EntityCollection'
], function(
createGuid,
defined,
defineProperties,
DeveloperError,
CesiumMath,
Entity,
EntityCollection) {
'use strict';
var entityOptionsScratch = {
id : undefined
};
var entityIdScratch = new Array(2);
function clean(entity) {
var propertyNames = entity.propertyNames;
var propertyNamesLength = propertyNames.length;
for (var i = 0; i < propertyNamesLength; i++) {
entity[propertyNames[i]] = undefined;
}
}
function subscribeToEntity(that, eventHash, collectionId, entity) {
entityIdScratch[0] = collectionId;
entityIdScratch[1] = entity.id;
eventHash[JSON.stringify(entityIdScratch)] = entity.definitionChanged.addEventListener(CompositeEntityCollection.prototype._onDefinitionChanged, that);
}
function unsubscribeFromEntity(that, eventHash, collectionId, entity) {
entityIdScratch[0] = collectionId;
entityIdScratch[1] = entity.id;
var id = JSON.stringify(entityIdScratch);
eventHash[id]();
eventHash[id] = undefined;
}
function recomposite(that) {
that._shouldRecomposite = true;
if (that._suspendCount !== 0) {
return;
}
var collections = that._collections;
var collectionsLength = collections.length;
var collectionsCopy = that._collectionsCopy;
var collectionsCopyLength = collectionsCopy.length;
var i;
var entity;
var entities;
var iEntities;
var collection;
var composite = that._composite;
var newEntities = new EntityCollection(that);
var eventHash = that._eventHash;
var collectionId;
for (i = 0; i < collectionsCopyLength; i++) {
collection = collectionsCopy[i];
collection.collectionChanged.removeEventListener(CompositeEntityCollection.prototype._onCollectionChanged, that);
entities = collection.values;
collectionId = collection.id;
for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {
entity = entities[iEntities];
unsubscribeFromEntity(that, eventHash, collectionId, entity);
}
}
for (i = collectionsLength - 1; i >= 0; i--) {
collection = collections[i];
collection.collectionChanged.addEventListener(CompositeEntityCollection.prototype._onCollectionChanged, that);
//Merge all of the existing entities.
entities = collection.values;
collectionId = collection.id;
for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {
entity = entities[iEntities];
subscribeToEntity(that, eventHash, collectionId, entity);
var compositeEntity = newEntities.getById(entity.id);
if (!defined(compositeEntity)) {
compositeEntity = composite.getById(entity.id);
if (!defined(compositeEntity)) {
entityOptionsScratch.id = entity.id;
compositeEntity = new Entity(entityOptionsScratch);
} else {
clean(compositeEntity);
}
newEntities.add(compositeEntity);
}
compositeEntity.merge(entity);
}
}
that._collectionsCopy = collections.slice(0);
composite.suspendEvents();
composite.removeAll();
var newEntitiesArray = newEntities.values;
for (i = 0; i < newEntitiesArray.length; i++) {
composite.add(newEntitiesArray[i]);
}
composite.resumeEvents();
}
/**
* Non-destructively composites multiple {@link EntityCollection} instances into a single collection.
* If a Entity with the same ID exists in multiple collections, it is non-destructively
* merged into a single new entity instance. If an entity has the same property in multiple
* collections, the property of the Entity in the last collection of the list it
* belongs to is used. CompositeEntityCollection can be used almost anywhere that a
* EntityCollection is used.
*
* @alias CompositeEntityCollection
* @constructor
*
* @param {EntityCollection[]} [collections] The initial list of EntityCollection instances to merge.
* @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
*/
function CompositeEntityCollection(collections, owner) {
this._owner = owner;
this._composite = new EntityCollection(this);
this._suspendCount = 0;
this._collections = defined(collections) ? collections.slice() : [];
this._collectionsCopy = [];
this._id = createGuid();
this._eventHash = {};
recomposite(this);
this._shouldRecomposite = false;
}
defineProperties(CompositeEntityCollection.prototype, {
/**
* Gets the event that is fired when entities are added or removed from the collection.
* The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
* @memberof CompositeEntityCollection.prototype
* @readonly
* @type {Event}
*/
collectionChanged : {
get : function() {
return this._composite._collectionChanged;
}
},
/**
* Gets a globally unique identifier for this collection.
* @memberof CompositeEntityCollection.prototype
* @readonly
* @type {String}
*/
id : {
get : function() {
return this._id;
}
},
/**
* Gets the array of Entity instances in the collection.
* This array should not be modified directly.
* @memberof CompositeEntityCollection.prototype
* @readonly
* @type {Entity[]}
*/
values : {
get : function() {
return this._composite.values;
}
},
/**
* Gets the owner of this composite entity collection, ie. the data source or composite entity collection which created it.
* @memberof CompositeEntityCollection.prototype
* @readonly
* @type {DataSource|CompositeEntityCollection}
*/
owner : {
get : function() {
return this._owner;
}
}
});
/**
* Adds a collection to the composite.
*
* @param {EntityCollection} collection the collection to add.
* @param {Number} [index] the index to add the collection at. If omitted, the collection will
* added on top of all existing collections.
*
* @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of collections.
*/
CompositeEntityCollection.prototype.addCollection = function(collection, index) {
var hasIndex = defined(index);
if (!defined(collection)) {
throw new DeveloperError('collection is required.');
}
if (hasIndex) {
if (index < 0) {
throw new DeveloperError('index must be greater than or equal to zero.');
} else if (index > this._collections.length) {
throw new DeveloperError('index must be less than or equal to the number of collections.');
}
}
if (!hasIndex) {
index = this._collections.length;
this._collections.push(collection);
} else {
this._collections.splice(index, 0, collection);
}
recomposite(this);
};
/**
* Removes a collection from this composite, if present.
*
* @param {EntityCollection} collection The collection to remove.
* @returns {Boolean} true if the collection was in the composite and was removed,
* false if the collection was not in the composite.
*/
CompositeEntityCollection.prototype.removeCollection = function(collection) {
var index = this._collections.indexOf(collection);
if (index !== -1) {
this._collections.splice(index, 1);
recomposite(this);
return true;
}
return false;
};
/**
* Removes all collections from this composite.
*/
CompositeEntityCollection.prototype.removeAllCollections = function() {
this._collections.length = 0;
recomposite(this);
};
/**
* Checks to see if the composite contains a given collection.
*
* @param {EntityCollection} collection the collection to check for.
* @returns {Boolean} true if the composite contains the collection, false otherwise.
*/
CompositeEntityCollection.prototype.containsCollection = function(collection) {
return this._collections.indexOf(collection) !== -1;
};
/**
* Returns true if the provided entity is in this collection, false otherwise.
*
* @param {Entity} entity The entity.
* @returns {Boolean} true if the provided entity is in this collection, false otherwise.
*/
CompositeEntityCollection.prototype.contains = function(entity) {
return this._composite.contains(entity);
};
/**
* Determines the index of a given collection in the composite.
*
* @param {EntityCollection} collection The collection to find the index of.
* @returns {Number} The index of the collection in the composite, or -1 if the collection does not exist in the composite.
*/
CompositeEntityCollection.prototype.indexOfCollection = function(collection) {
return this._collections.indexOf(collection);
};
/**
* Gets a collection by index from the composite.
*
* @param {Number} index the index to retrieve.
*/
CompositeEntityCollection.prototype.getCollection = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.', 'index');
}
return this._collections[index];
};
/**
* Gets the number of collections in this composite.
*/
CompositeEntityCollection.prototype.getCollectionsLength = function() {
return this._collections.length;
};
function getCollectionIndex(collections, collection) {
if (!defined(collection)) {
throw new DeveloperError('collection is required.');
}
var index = collections.indexOf(collection);
if (index === -1) {
throw new DeveloperError('collection is not in this composite.');
}
return index;
}
function swapCollections(composite, i, j) {
var arr = composite._collections;
i = CesiumMath.clamp(i, 0, arr.length - 1);
j = CesiumMath.clamp(j, 0, arr.length - 1);
if (i === j) {
return;
}
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
recomposite(composite);
}
/**
* Raises a collection up one position in the composite.
*
* @param {EntityCollection} collection the collection to move.
*
* @exception {DeveloperError} collection is not in this composite.
*/
CompositeEntityCollection.prototype.raiseCollection = function(collection) {
var index = getCollectionIndex(this._collections, collection);
swapCollections(this, index, index + 1);
};
/**
* Lowers a collection down one position in the composite.
*
* @param {EntityCollection} collection the collection to move.
*
* @exception {DeveloperError} collection is not in this composite.
*/
CompositeEntityCollection.prototype.lowerCollection = function(collection) {
var index = getCollectionIndex(this._collections, collection);
swapCollections(this, index, index - 1);
};
/**
* Raises a collection to the top of the composite.
*
* @param {EntityCollection} collection the collection to move.
*
* @exception {DeveloperError} collection is not in this composite.
*/
CompositeEntityCollection.prototype.raiseCollectionToTop = function(collection) {
var index = getCollectionIndex(this._collections, collection);
if (index === this._collections.length - 1) {
return;
}
this._collections.splice(index, 1);
this._collections.push(collection);
recomposite(this);
};
/**
* Lowers a collection to the bottom of the composite.
*
* @param {EntityCollection} collection the collection to move.
*
* @exception {DeveloperError} collection is not in this composite.
*/
CompositeEntityCollection.prototype.lowerCollectionToBottom = function(collection) {
var index = getCollectionIndex(this._collections, collection);
if (index === 0) {
return;
}
this._collections.splice(index, 1);
this._collections.splice(0, 0, collection);
recomposite(this);
};
/**
* Prevents {@link EntityCollection#collectionChanged} events from being raised
* until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
* point a single event will be raised that covers all suspended operations.
* This allows for many items to be added and removed efficiently.
* While events are suspended, recompositing of the collections will
* also be suspended, as this can be a costly operation.
* This function can be safely called multiple times as long as there
* are corresponding calls to {@link EntityCollection#resumeEvents}.
*/
CompositeEntityCollection.prototype.suspendEvents = function() {
this._suspendCount++;
this._composite.suspendEvents();
};
/**
* Resumes raising {@link EntityCollection#collectionChanged} events immediately
* when an item is added or removed. Any modifications made while while events were suspended
* will be triggered as a single event when this function is called. This function also ensures
* the collection is recomposited if events are also resumed.
* This function is reference counted and can safely be called multiple times as long as there
* are corresponding calls to {@link EntityCollection#resumeEvents}.
*
* @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
*/
CompositeEntityCollection.prototype.resumeEvents = function() {
if (this._suspendCount === 0) {
throw new DeveloperError('resumeEvents can not be called before suspendEvents.');
}
this._suspendCount--;
// recomposite before triggering events (but only if required for performance) that might depend on a composited collection
if (this._shouldRecomposite && this._suspendCount === 0) {
recomposite(this);
this._shouldRecomposite = false;
}
this._composite.resumeEvents();
};
/**
* Computes the maximum availability of the entities in the collection.
* If the collection contains a mix of infinitely available data and non-infinite data,
* It will return the interval pertaining to the non-infinite data only. If all
* data is infinite, an infinite interval will be returned.
*
* @returns {TimeInterval} The availability of entities in the collection.
*/
CompositeEntityCollection.prototype.computeAvailability = function() {
return this._composite.computeAvailability();
};
/**
* Gets an entity with the specified id.
*
* @param {Object} id The id of the entity to retrieve.
* @returns {Entity} The entity with the provided id or undefined if the id did not exist in the collection.
*/
CompositeEntityCollection.prototype.getById = function(id) {
return this._composite.getById(id);
};
CompositeEntityCollection.prototype._onCollectionChanged = function(collection, added, removed) {
var collections = this._collectionsCopy;
var collectionsLength = collections.length;
var composite = this._composite;
composite.suspendEvents();
var i;
var q;
var entity;
var compositeEntity;
var removedLength = removed.length;
var eventHash = this._eventHash;
var collectionId = collection.id;
for (i = 0; i < removedLength; i++) {
var removedEntity = removed[i];
unsubscribeFromEntity(this, eventHash, collectionId, removedEntity);
var removedId = removedEntity.id;
//Check if the removed entity exists in any of the remaining collections
//If so, we clean and remerge it.
for (q = collectionsLength - 1; q >= 0; q--) {
entity = collections[q].getById(removedId);
if (defined(entity)) {
if (!defined(compositeEntity)) {
compositeEntity = composite.getById(removedId);
clean(compositeEntity);
}
compositeEntity.merge(entity);
}
}
//We never retrieved the compositeEntity, which means it no longer
//exists in any of the collections, remove it from the composite.
if (!defined(compositeEntity)) {
composite.removeById(removedId);
}
compositeEntity = undefined;
}
var addedLength = added.length;
for (i = 0; i < addedLength; i++) {
var addedEntity = added[i];
subscribeToEntity(this, eventHash, collectionId, addedEntity);
var addedId = addedEntity.id;
//We know the added entity exists in at least one collection,
//but we need to check all collections and re-merge in order
//to maintain the priority of properties.
for (q = collectionsLength - 1; q >= 0; q--) {
entity = collections[q].getById(addedId);
if (defined(entity)) {
if (!defined(compositeEntity)) {
compositeEntity = composite.getById(addedId);
if (!defined(compositeEntity)) {
entityOptionsScratch.id = addedId;
compositeEntity = new Entity(entityOptionsScratch);
composite.add(compositeEntity);
} else {
clean(compositeEntity);
}
}
compositeEntity.merge(entity);
}
}
compositeEntity = undefined;
}
composite.resumeEvents();
};
CompositeEntityCollection.prototype._onDefinitionChanged = function(entity, propertyName, newValue, oldValue) {
var collections = this._collections;
var composite = this._composite;
var collectionsLength = collections.length;
var id = entity.id;
var compositeEntity = composite.getById(id);
var compositeProperty = compositeEntity[propertyName];
var newProperty = !defined(compositeProperty);
var firstTime = true;
for (var q = collectionsLength - 1; q >= 0; q--) {
var innerEntity = collections[q].getById(entity.id);
if (defined(innerEntity)) {
var property = innerEntity[propertyName];
if (defined(property)) {
if (firstTime) {
firstTime = false;
//We only want to clone if the property is also mergeable.
//This ensures that leaf properties are referenced and not copied,
//which is the entire point of compositing.
if (defined(property.merge) && defined(property.clone)) {
compositeProperty = property.clone(compositeProperty);
} else {
compositeProperty = property;
break;
}
}
compositeProperty.merge(property);
}
}
}
if (newProperty && compositeEntity.propertyNames.indexOf(propertyName) === -1) {
compositeEntity.addProperty(propertyName);
}
compositeEntity[propertyName] = compositeProperty;
};
return CompositeEntityCollection;
});
/*global define*/
define('DataSources/CompositeProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/EventHelper',
'../Core/TimeIntervalCollection',
'./Property'
], function(
defined,
defineProperties,
DeveloperError,
Event,
EventHelper,
TimeIntervalCollection,
Property) {
'use strict';
function subscribeAll(property, eventHelper, definitionChanged, intervals) {
function callback() {
definitionChanged.raiseEvent(property);
}
var items = [];
eventHelper.removeAll();
var length = intervals.length;
for (var i = 0; i < length; i++) {
var interval = intervals.get(i);
if (defined(interval.data) && items.indexOf(interval.data) === -1) {
eventHelper.add(interval.data.definitionChanged, callback);
}
}
}
/**
* A {@link Property} which is defined by a {@link TimeIntervalCollection}, where the
* data property of each {@link TimeInterval} is another Property instance which is
* evaluated at the provided time.
*
* @alias CompositeProperty
* @constructor
*
*
* @example
* var constantProperty = ...;
* var sampledProperty = ...;
*
* //Create a composite property from two previously defined properties
* //where the property is valid on August 1st, 2012 and uses a constant
* //property for the first half of the day and a sampled property for the
* //remaining half.
* var composite = new Cesium.CompositeProperty();
* composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
* iso8601 : '2012-08-01T00:00:00.00Z/2012-08-01T12:00:00.00Z',
* data : constantProperty
* }));
* composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
* iso8601 : '2012-08-01T12:00:00.00Z/2012-08-02T00:00:00.00Z',
* isStartIncluded : false,
* isStopIncluded : false,
* data : sampledProperty
* }));
*
* @see CompositeMaterialProperty
* @see CompositePositionProperty
*/
function CompositeProperty() {
this._eventHelper = new EventHelper();
this._definitionChanged = new Event();
this._intervals = new TimeIntervalCollection();
this._intervals.changedEvent.addEventListener(CompositeProperty.prototype._intervalsChanged, this);
}
defineProperties(CompositeProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof CompositeProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._intervals.isEmpty;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value.
* @memberof CompositeProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the interval collection.
* @memberof CompositeProperty.prototype
*
* @type {TimeIntervalCollection}
*/
intervals : {
get : function() {
return this._intervals;
}
}
});
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
CompositeProperty.prototype.getValue = function(time, result) {
if (!defined(time)) {
throw new DeveloperError('time is required');
}
var innerProperty = this._intervals.findDataForIntervalContainingDate(time);
if (defined(innerProperty)) {
return innerProperty.getValue(time, result);
}
return undefined;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
CompositeProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof CompositeProperty && //
this._intervals.equals(other._intervals, Property.equals));
};
/**
* @private
*/
CompositeProperty.prototype._intervalsChanged = function() {
subscribeAll(this, this._eventHelper, this._definitionChanged, this._intervals);
this._definitionChanged.raiseEvent(this);
};
return CompositeProperty;
});
/*global define*/
define('DataSources/CompositeMaterialProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./CompositeProperty',
'./Property'
], function(
defined,
defineProperties,
DeveloperError,
Event,
CompositeProperty,
Property) {
'use strict';
/**
* A {@link CompositeProperty} which is also a {@link MaterialProperty}.
*
* @alias CompositeMaterialProperty
* @constructor
*/
function CompositeMaterialProperty() {
this._definitionChanged = new Event();
this._composite = new CompositeProperty();
this._composite.definitionChanged.addEventListener(CompositeMaterialProperty.prototype._raiseDefinitionChanged, this);
}
defineProperties(CompositeMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof CompositeMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._composite.isConstant;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value.
* @memberof CompositeMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the interval collection.
* @memberof CompositeMaterialProperty.prototype
*
* @type {TimeIntervalCollection}
*/
intervals : {
get : function() {
return this._composite._intervals;
}
}
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
CompositeMaterialProperty.prototype.getType = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required');
}
var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time);
if (defined(innerProperty)) {
return innerProperty.getType(time);
}
return undefined;
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
CompositeMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(time)) {
throw new DeveloperError('time is required');
}
var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time);
if (defined(innerProperty)) {
return innerProperty.getValue(time, result);
}
return undefined;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
CompositeMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof CompositeMaterialProperty && //
this._composite.equals(other._composite, Property.equals));
};
/**
* @private
*/
CompositeMaterialProperty.prototype._raiseDefinitionChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return CompositeMaterialProperty;
});
/*global define*/
define('DataSources/CompositePositionProperty',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/ReferenceFrame',
'./CompositeProperty',
'./Property'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
ReferenceFrame,
CompositeProperty,
Property) {
'use strict';
/**
* A {@link CompositeProperty} which is also a {@link PositionProperty}.
*
* @alias CompositePositionProperty
* @constructor
*
* @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
*/
function CompositePositionProperty(referenceFrame) {
this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
this._definitionChanged = new Event();
this._composite = new CompositeProperty();
this._composite.definitionChanged.addEventListener(CompositePositionProperty.prototype._raiseDefinitionChanged, this);
}
defineProperties(CompositePositionProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof CompositePositionProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._composite.isConstant;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value.
* @memberof CompositePositionProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the interval collection.
* @memberof CompositePositionProperty.prototype
*
* @type {TimeIntervalCollection}
*/
intervals : {
get : function() {
return this._composite.intervals;
}
},
/**
* Gets or sets the reference frame which this position presents itself as.
* Each PositionProperty making up this object has it's own reference frame,
* so this property merely exposes a "preferred" reference frame for clients
* to use.
* @memberof CompositePositionProperty.prototype
*
* @type {ReferenceFrame}
*/
referenceFrame : {
get : function() {
return this._referenceFrame;
},
set : function(value) {
this._referenceFrame = value;
}
}
});
/**
* Gets the value of the property at the provided time in the fixed frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
CompositePositionProperty.prototype.getValue = function(time, result) {
return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
};
/**
* Gets the value of the property at the provided time and in the provided reference frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
CompositePositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(referenceFrame)) {
throw new DeveloperError('referenceFrame is required.');
}
var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time);
if (defined(innerProperty)) {
return innerProperty.getValueInReferenceFrame(time, referenceFrame, result);
}
return undefined;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
CompositePositionProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof CompositePositionProperty && //
this._referenceFrame === other._referenceFrame && //
this._composite.equals(other._composite, Property.equals));
};
/**
* @private
*/
CompositePositionProperty.prototype._raiseDefinitionChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return CompositePositionProperty;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ShadowVolumeFS',[],function() {
'use strict';
return "#extension GL_EXT_frag_depth : enable\n\
varying float v_WindowZ;\n\
varying vec4 v_color;\n\
void writeDepthClampedToFarPlane()\n\
{\n\
gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n\
}\n\
void main(void)\n\
{\n\
gl_FragColor = v_color;\n\
writeDepthClampedToFarPlane();\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ShadowVolumeVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
varying float v_WindowZ;\n\
varying vec4 v_color;\n\
vec4 depthClampFarPlane(vec4 vertexInClipCoordinates)\n\
{\n\
v_WindowZ = (0.5 * (vertexInClipCoordinates.z / vertexInClipCoordinates.w) + 0.5) * vertexInClipCoordinates.w;\n\
vertexInClipCoordinates.z = min(vertexInClipCoordinates.z, vertexInClipCoordinates.w);\n\
return vertexInClipCoordinates;\n\
}\n\
void main()\n\
{\n\
v_color = color;\n\
vec4 position = czm_computePosition();\n\
gl_Position = depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position);\n\
}\n\
";
});
/*global define*/
define('Scene/DepthFunction',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Determines the function used to compare two depths for the depth test.
*
* @exports DepthFunction
*/
var DepthFunction = {
/**
* The depth test never passes.
*
* @type {Number}
* @constant
*/
NEVER : WebGLConstants.NEVER,
/**
* The depth test passes if the incoming depth is less than the stored depth.
*
* @type {Number}
* @constant
*/
LESS : WebGLConstants.LESS,
/**
* The depth test passes if the incoming depth is equal to the stored depth.
*
* @type {Number}
* @constant
*/
EQUAL : WebGLConstants.EQUAL,
/**
* The depth test passes if the incoming depth is less than or equal to the stored depth.
*
* @type {Number}
* @constant
*/
LESS_OR_EQUAL : WebGLConstants.LEQUAL,
/**
* The depth test passes if the incoming depth is greater than the stored depth.
*
* @type {Number}
* @constant
*/
GREATER : WebGLConstants.GREATER,
/**
* The depth test passes if the incoming depth is not equal to the stored depth.
*
* @type {Number}
* @constant
*/
NOT_EQUAL : WebGLConstants.NOTEQUAL,
/**
* The depth test passes if the incoming depth is greater than or equal to the stored depth.
*
* @type {Number}
* @constant
*/
GREATER_OR_EQUAL : WebGLConstants.GEQUAL,
/**
* The depth test always passes.
*
* @type {Number}
* @constant
*/
ALWAYS : WebGLConstants.ALWAYS
};
return freezeObject(DepthFunction);
});
/*global define*/
define('Scene/StencilFunction',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Determines the function used to compare stencil values for the stencil test.
*
* @exports StencilFunction
*/
var StencilFunction = {
/**
* The stencil test never passes.
*
* @type {Number}
* @constant
*/
NEVER : WebGLConstants.NEVER,
/**
* The stencil test passes when the masked reference value is less than the masked stencil value.
*
* @type {Number}
* @constant
*/
LESS : WebGLConstants.LESS,
/**
* The stencil test passes when the masked reference value is equal to the masked stencil value.
*
* @type {Number}
* @constant
*/
EQUAL : WebGLConstants.EQUAL,
/**
* The stencil test passes when the masked reference value is less than or equal to the masked stencil value.
*
* @type {Number}
* @constant
*/
LESS_OR_EQUAL : WebGLConstants.LEQUAL,
/**
* The stencil test passes when the masked reference value is greater than the masked stencil value.
*
* @type {Number}
* @constant
*/
GREATER : WebGLConstants.GREATER,
/**
* The stencil test passes when the masked reference value is not equal to the masked stencil value.
*
* @type {Number}
* @constant
*/
NOT_EQUAL : WebGLConstants.NOTEQUAL,
/**
* The stencil test passes when the masked reference value is greater than or equal to the masked stencil value.
*
* @type {Number}
* @constant
*/
GREATER_OR_EQUAL : WebGLConstants.GEQUAL,
/**
* The stencil test always passes.
*
* @type {Number}
* @constant
*/
ALWAYS : WebGLConstants.ALWAYS
};
return freezeObject(StencilFunction);
});
/*global define*/
define('Scene/StencilOperation',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* Determines the action taken based on the result of the stencil test.
*
* @exports StencilOperation
*/
var StencilOperation = {
/**
* Sets the stencil buffer value to zero.
*
* @type {Number}
* @constant
*/
ZERO : WebGLConstants.ZERO,
/**
* Does not change the stencil buffer.
*
* @type {Number}
* @constant
*/
KEEP : WebGLConstants.KEEP,
/**
* Replaces the stencil buffer value with the reference value.
*
* @type {Number}
* @constant
*/
REPLACE : WebGLConstants.REPLACE,
/**
* Increments the stencil buffer value, clamping to unsigned byte.
*
* @type {Number}
* @constant
*/
INCREMENT : WebGLConstants.INCR,
/**
* Decrements the stencil buffer value, clamping to zero.
*
* @type {Number}
* @constant
*/
DECREMENT : WebGLConstants.DECR,
/**
* Bitwise inverts the existing stencil buffer value.
*
* @type {Number}
* @constant
*/
INVERT : WebGLConstants.INVERT,
/**
* Increments the stencil buffer value, wrapping to zero when exceeding the unsigned byte range.
*
* @type {Number}
* @constant
*/
INCREMENT_WRAP : WebGLConstants.INCR_WRAP,
/**
* Decrements the stencil buffer value, wrapping to the maximum unsigned byte instead of going below zero.
*
* @type {Number}
* @constant
*/
DECREMENT_WRAP : WebGLConstants.DECR_WRAP
};
return freezeObject(StencilOperation);
});
/*global define*/
define('Scene/GroundPrimitive',[
'../Core/BoundingSphere',
'../Core/buildModuleUrl',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/GeographicTilingScheme',
'../Core/GeometryInstance',
'../Core/isArray',
'../Core/loadJson',
'../Core/Math',
'../Core/OrientedBoundingBox',
'../Core/Rectangle',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Shaders/ShadowVolumeFS',
'../Shaders/ShadowVolumeVS',
'../ThirdParty/when',
'./BlendingState',
'./DepthFunction',
'./PerInstanceColorAppearance',
'./Primitive',
'./SceneMode',
'./StencilFunction',
'./StencilOperation'
], function(
BoundingSphere,
buildModuleUrl,
Cartesian2,
Cartesian3,
Cartographic,
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
GeographicTilingScheme,
GeometryInstance,
isArray,
loadJson,
CesiumMath,
OrientedBoundingBox,
Rectangle,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
ShaderSource,
ShadowVolumeFS,
ShadowVolumeVS,
when,
BlendingState,
DepthFunction,
PerInstanceColorAppearance,
Primitive,
SceneMode,
StencilFunction,
StencilOperation) {
'use strict';
/**
* A ground primitive represents geometry draped over the terrain in the {@link Scene}. The geometry must be from a single {@link GeometryInstance}.
* Batching multiple geometries is not yet supported.
*
* A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including
* {@link Material} and {@link RenderState}. Roughly, the geometry instance defines the structure and placement,
* and the appearance defines the visual characteristics. Decoupling geometry and appearance allows us to mix
* and match most of them and add a new geometry or appearance independently of each other. Only the {@link PerInstanceColorAppearance}
* is supported at this time.
*
*
* Because of the cutting edge nature of this feature in WebGL, it requires the EXT_frag_depth extension, which is currently only supported in Chrome,
* Firefox, and Edge. Apple support is expected in iOS 9 and MacOS Safari 9. Android support varies by hardware and IE11 will most likely never support
* it. You can use webglreport.com to verify support for your hardware.
*
*
* Valid geometries are {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}.
*
*
* @alias GroundPrimitive
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
* @param {Boolean} [options.vertexCacheOptimize=false] When true
, geometry vertices are optimized for the pre and post-vertex-shader caches.
* @param {Boolean} [options.interleave=false] When true
, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
* @param {Boolean} [options.compressVertices=true] When true
, the geometry vertices are compressed, which will save memory.
* @param {Boolean} [options.releaseGeometryInstances=true] When true
, the primitive does not keep a reference to the input geometryInstances
to save memory.
* @param {Boolean} [options.allowPicking=true] When true
, each geometry instance will only be pickable with {@link Scene#pick}. When false
, GPU memory is saved.
* @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
* @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be true
on
* creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be false
.
*
* @example
* // Example 1: Create primitive with a single instance
* var rectangleInstance = new Cesium.GeometryInstance({
* geometry : new Cesium.RectangleGeometry({
* rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
* }),
* id : 'rectangle',
* attributes : {
* color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
* }
* });
* scene.primitives.add(new Cesium.GroundPrimitive({
* geometryInstances : rectangleInstance
* }));
*
* // Example 2: Batch instances
* var color = new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5); // Both instances must have the same color.
* var rectangleInstance = new Cesium.GeometryInstance({
* geometry : new Cesium.RectangleGeometry({
* rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
* }),
* id : 'rectangle',
* attributes : {
* color : color
* }
* });
* var ellipseInstance = new Cesium.GeometryInstance({
* geometry : new Cesium.EllipseGeometry({
* center : Cesium.Cartesian3.fromDegrees(-105.0, 40.0),
* semiMinorAxis : 300000.0,
* semiMajorAxis : 400000.0
* }),
* id : 'ellipse',
* attributes : {
* color : color
* }
* });
* scene.primitives.add(new Cesium.GroundPrimitive({
* geometryInstances : [rectangleInstance, ellipseInstance]
* }));
*
* @see Primitive
* @see GeometryInstance
* @see Appearance
*/
function GroundPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The geometry instance rendered with this primitive. This may
* be undefined
if options.releaseGeometryInstances
* is true
when the primitive is constructed.
*
* Changing this property after the primitive is rendered has no effect.
*
*
* Because of the rendering technique used, all geometry instances must be the same color.
* If there is an instance with a differing color, a DeveloperError
will be thrown
* on the first attempt to render.
*
*
* @type {Array|GeometryInstance}
*
* @default undefined
*/
this.geometryInstances = options.geometryInstances;
/**
* Determines if the primitive will be shown. This affects all geometry
* instances in the primitive.
*
* @type {Boolean}
*
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the shadow volume for each geometry in the primitive. Must be true
on
* creation for the volumes to be created before the geometry is released or releaseGeometryInstances
* must be false
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false);
this._sp = undefined;
this._spPick = undefined;
this._rsStencilPreloadPass = undefined;
this._rsStencilDepthPass = undefined;
this._rsColorPass = undefined;
this._rsPickPass = undefined;
this._uniformMap = {};
this._boundingVolumes = [];
this._boundingVolumes2D = [];
this._ready = false;
this._readyPromise = when.defer();
this._primitive = undefined;
this._debugPrimitive = undefined;
this._maxHeight = undefined;
this._minHeight = undefined;
this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
this._boundingSpheresKeys = [];
this._boundingSpheres = [];
var appearance = new PerInstanceColorAppearance({
flat : true
});
var readOnlyAttributes;
if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) {
readOnlyAttributes = readOnlyInstanceAttributesScratch;
}
this._primitiveOptions = {
geometryInstances : undefined,
appearance : appearance,
vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false),
interleave : defaultValue(options.interleave, false),
releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true),
allowPicking : defaultValue(options.allowPicking, true),
asynchronous : defaultValue(options.asynchronous, true),
compressVertices : defaultValue(options.compressVertices, true),
_readOnlyInstanceAttributes : readOnlyAttributes,
_createRenderStatesFunction : undefined,
_createShaderProgramFunction : undefined,
_createCommandsFunction : undefined,
_createPickOffsets : true
};
}
var readOnlyInstanceAttributesScratch = ['color'];
defineProperties(GroundPrimitive.prototype, {
/**
* When true
, geometry vertices are optimized for the pre and post-vertex-shader caches.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
vertexCacheOptimize : {
get : function() {
return this._primitiveOptions.vertexCacheOptimize;
}
},
/**
* Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
interleave : {
get : function() {
return this._primitiveOptions.interleave;
}
},
/**
* When true
, the primitive does not keep a reference to the input geometryInstances
to save memory.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
releaseGeometryInstances : {
get : function() {
return this._primitiveOptions.releaseGeometryInstances;
}
},
/**
* When true
, each geometry instance will only be pickable with {@link Scene#pick}. When false
, GPU memory is saved.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
allowPicking : {
get : function() {
return this._primitiveOptions.allowPicking;
}
},
/**
* Determines if the geometry instances will be created and batched on a web worker.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
asynchronous : {
get : function() {
return this._primitiveOptions.asynchronous;
}
},
/**
* When true
, geometry vertices are compressed, which will save memory.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
compressVertices : {
get : function() {
return this._primitiveOptions.compressVertices;
}
},
/**
* Determines if the primitive is complete and ready to render. If this property is
* true, the primitive will be rendered the next time that {@link GroundPrimitive#update}
* is called.
*
* @memberof GroundPrimitive.prototype
*
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves when the primitive is ready to render.
* @memberof GroundPrimitive.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
}
});
/**
* Determines if GroundPrimitive rendering is supported.
*
* @param {Scene} scene The scene.
* @returns {Boolean} true
if GroundPrimitives are supported; otherwise, returns false
*/
GroundPrimitive.isSupported = function(scene) {
return scene.context.fragmentDepth && scene.context.stencilBuffer;
};
GroundPrimitive._defaultMaxTerrainHeight = 9000.0;
GroundPrimitive._defaultMinTerrainHeight = -100000.0;
GroundPrimitive._terrainHeights = undefined;
GroundPrimitive._terrainHeightsMaxLevel = 6;
function getComputeMaximumHeightFunction(primitive) {
return function(granularity, ellipsoid) {
var r = ellipsoid.maximumRadius;
var delta = (r / Math.cos(granularity * 0.5)) - r;
return primitive._maxHeight + delta;
};
}
function getComputeMinimumHeightFunction(primitive) {
return function(granularity, ellipsoid) {
return primitive._minHeight;
};
}
var stencilPreloadRenderState = {
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
},
stencilTest : {
enabled : true,
frontFunction : StencilFunction.ALWAYS,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.DECREMENT_WRAP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : StencilFunction.ALWAYS,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.INCREMENT_WRAP,
zPass : StencilOperation.INCREMENT_WRAP
},
reference : 0,
mask : ~0
},
depthTest : {
enabled : false
},
depthMask : false
};
var stencilDepthRenderState = {
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
},
stencilTest : {
enabled : true,
frontFunction : StencilFunction.ALWAYS,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.INCREMENT_WRAP
},
backFunction : StencilFunction.ALWAYS,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
reference : 0,
mask : ~0
},
depthTest : {
enabled : true,
func : DepthFunction.LESS_OR_EQUAL
},
depthMask : false
};
var colorRenderState = {
stencilTest : {
enabled : true,
frontFunction : StencilFunction.NOT_EQUAL,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : StencilFunction.NOT_EQUAL,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
reference : 0,
mask : ~0
},
depthTest : {
enabled : false
},
depthMask : false,
blending : BlendingState.ALPHA_BLEND
};
var pickRenderState = {
stencilTest : {
enabled : true,
frontFunction : StencilFunction.NOT_EQUAL,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : StencilFunction.NOT_EQUAL,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
reference : 0,
mask : ~0
},
depthTest : {
enabled : false
},
depthMask : false
};
var scratchBVCartesianHigh = new Cartesian3();
var scratchBVCartesianLow = new Cartesian3();
var scratchBVCartesian = new Cartesian3();
var scratchBVCartographic = new Cartographic();
var scratchBVRectangle = new Rectangle();
var tilingScheme = new GeographicTilingScheme();
var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
var scratchTileXY = new Cartesian2();
function getRectangle(frameState, geometry) {
var ellipsoid = frameState.mapProjection.ellipsoid;
if (!defined(geometry.attributes) || !defined(geometry.attributes.position3DHigh)) {
if (defined(geometry.rectangle)) {
return geometry.rectangle;
}
return undefined;
}
var highPositions = geometry.attributes.position3DHigh.values;
var lowPositions = geometry.attributes.position3DLow.values;
var length = highPositions.length;
var minLat = Number.POSITIVE_INFINITY;
var minLon = Number.POSITIVE_INFINITY;
var maxLat = Number.NEGATIVE_INFINITY;
var maxLon = Number.NEGATIVE_INFINITY;
for (var i = 0; i < length; i +=3) {
var highPosition = Cartesian3.unpack(highPositions, i, scratchBVCartesianHigh);
var lowPosition = Cartesian3.unpack(lowPositions, i, scratchBVCartesianLow);
var position = Cartesian3.add(highPosition, lowPosition, scratchBVCartesian);
var cartographic = ellipsoid.cartesianToCartographic(position, scratchBVCartographic);
var latitude = cartographic.latitude;
var longitude = cartographic.longitude;
minLat = Math.min(minLat, latitude);
minLon = Math.min(minLon, longitude);
maxLat = Math.max(maxLat, latitude);
maxLon = Math.max(maxLon, longitude);
}
var rectangle = scratchBVRectangle;
rectangle.north = maxLat;
rectangle.south = minLat;
rectangle.east = maxLon;
rectangle.west = minLon;
return rectangle;
}
var scratchDiagonalCartesianNE = new Cartesian3();
var scratchDiagonalCartesianSW = new Cartesian3();
var scratchDiagonalCartographic = new Cartographic();
var scratchCenterCartesian = new Cartesian3();
var scratchSurfaceCartesian = new Cartesian3();
function getTileXYLevel(rectangle) {
Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]);
Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]);
Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]);
Cartographic.fromRadians(rectangle.west, rectangle.south, 0.0, scratchCorners[3]);
// Determine which tile the bounding rectangle is in
var lastLevelX = 0, lastLevelY = 0;
var currentX = 0, currentY = 0;
var maxLevel = GroundPrimitive._terrainHeightsMaxLevel;
for(var i = 0; i <= maxLevel; ++i) {
var failed = false;
for(var j = 0; j < 4; ++j) {
var corner = scratchCorners[j];
tilingScheme.positionToTileXY(corner, i, scratchTileXY);
if (j === 0) {
currentX = scratchTileXY.x;
currentY = scratchTileXY.y;
} else if(currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
failed = true;
break;
}
}
if (failed) {
break;
}
lastLevelX = currentX;
lastLevelY = currentY;
}
if (i === 0) {
return undefined;
}
return {
x : lastLevelX,
y : lastLevelY,
level : (i > maxLevel) ? maxLevel : (i - 1)
};
}
function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) {
var xyLevel = getTileXYLevel(rectangle);
// Get the terrain min/max for that tile
var minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
if (defined(xyLevel)) {
var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
var heights = GroundPrimitive._terrainHeights[key];
if (defined(heights)) {
minTerrainHeight = heights[0];
maxTerrainHeight = heights[1];
}
// Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle, scratchDiagonalCartographic),
scratchDiagonalCartesianNE);
ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle, scratchDiagonalCartographic),
scratchDiagonalCartesianSW);
Cartesian3.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian);
Cartesian3.add(scratchDiagonalCartesianNE,
Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian);
var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian);
if (defined(surfacePosition)) {
var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition);
minTerrainHeight = Math.min(minTerrainHeight, -distance);
} else {
minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
}
}
primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight);
primitive._maxTerrainHeight = maxTerrainHeight;
}
var scratchBoundingSphere = new BoundingSphere();
function getInstanceBoundingSphere(rectangle, ellipsoid) {
var xyLevel = getTileXYLevel(rectangle);
// Get the terrain max for that tile
var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
if (defined(xyLevel)) {
var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
var heights = GroundPrimitive._terrainHeights[key];
if (defined(heights)) {
maxTerrainHeight = heights[1];
}
}
var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere);
return BoundingSphere.union(result, scratchBoundingSphere, result);
}
function createBoundingVolume(primitive, frameState, geometry) {
var ellipsoid = frameState.mapProjection.ellipsoid;
var rectangle = getRectangle(frameState, geometry);
// Use an oriented bounding box by default, but switch to a bounding sphere if bounding box creation would fail.
if (rectangle.width < CesiumMath.PI) {
var obb = OrientedBoundingBox.fromRectangle(rectangle, primitive._maxHeight, primitive._minHeight, ellipsoid);
primitive._boundingVolumes.push(obb);
} else {
var highPositions = geometry.attributes.position3DHigh.values;
var lowPositions = geometry.attributes.position3DLow.values;
primitive._boundingVolumes.push(BoundingSphere.fromEncodedCartesianVertices(highPositions, lowPositions));
}
if (!frameState.scene3DOnly) {
var projection = frameState.mapProjection;
var boundingVolume = BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, primitive._maxHeight, primitive._minHeight);
Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
primitive._boundingVolumes2D.push(boundingVolume);
}
}
function createRenderStates(primitive, context, appearance, twoPasses) {
if (defined(primitive._rsStencilPreloadPass)) {
return;
}
primitive._rsStencilPreloadPass = RenderState.fromCache(stencilPreloadRenderState);
primitive._rsStencilDepthPass = RenderState.fromCache(stencilDepthRenderState);
primitive._rsColorPass = RenderState.fromCache(colorRenderState);
primitive._rsPickPass = RenderState.fromCache(pickRenderState);
}
function createShaderProgram(primitive, frameState, appearance) {
if (defined(primitive._sp)) {
return;
}
var context = frameState.context;
var vs = ShadowVolumeVS;
vs = primitive._primitive._batchTable.getVertexShaderCallback()(vs);
vs = Primitive._appendShowToShader(primitive._primitive, vs);
vs = Primitive._appendDistanceDisplayConditionToShader(primitive._primitive, vs);
vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
vs = Primitive._updateColorAttribute(primitive._primitive, vs);
var fs = ShadowVolumeFS;
var attributeLocations = primitive._primitive._attributeLocations;
primitive._sp = ShaderProgram.replaceCache({
context : context,
shaderProgram : primitive._sp,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
if (primitive._primitive.allowPicking) {
var vsPick = ShaderSource.createPickVertexShaderSource(vs);
vsPick = Primitive._updatePickColorAttribute(vsPick);
var pickFS = new ShaderSource({
sources : [fs],
pickColorQualifier : 'varying'
});
primitive._spPick = ShaderProgram.replaceCache({
context : context,
shaderProgram : primitive._spPick,
vertexShaderSource : vsPick,
fragmentShaderSource : pickFS,
attributeLocations : attributeLocations
});
} else {
primitive._spPick = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
}
}
function createColorCommands(groundPrimitive, colorCommands) {
var primitive = groundPrimitive._primitive;
var length = primitive._va.length * 3;
colorCommands.length = length;
var vaIndex = 0;
var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap);
for (var i = 0; i < length; i += 3) {
var vertexArray = primitive._va[vaIndex++];
// stencil preload command
var command = colorCommands[i];
if (!defined(command)) {
command = colorCommands[i] = new DrawCommand({
owner : groundPrimitive,
primitiveType : primitive._primitiveType
});
}
command.vertexArray = vertexArray;
command.renderState = groundPrimitive._rsStencilPreloadPass;
command.shaderProgram = groundPrimitive._sp;
command.uniformMap = uniformMap;
command.pass = Pass.GROUND;
// stencil depth command
command = colorCommands[i + 1];
if (!defined(command)) {
command = colorCommands[i + 1] = new DrawCommand({
owner : groundPrimitive,
primitiveType : primitive._primitiveType
});
}
command.vertexArray = vertexArray;
command.renderState = groundPrimitive._rsStencilDepthPass;
command.shaderProgram = groundPrimitive._sp;
command.uniformMap = uniformMap;
command.pass = Pass.GROUND;
// color command
command = colorCommands[i + 2];
if (!defined(command)) {
command = colorCommands[i + 2] = new DrawCommand({
owner : groundPrimitive,
primitiveType : primitive._primitiveType
});
}
command.vertexArray = vertexArray;
command.renderState = groundPrimitive._rsColorPass;
command.shaderProgram = groundPrimitive._sp;
command.uniformMap = uniformMap;
command.pass = Pass.GROUND;
}
}
function createPickCommands(groundPrimitive, pickCommands) {
var primitive = groundPrimitive._primitive;
var pickOffsets = primitive._pickOffsets;
var length = pickOffsets.length * 3;
pickCommands.length = length;
var pickIndex = 0;
var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap);
for (var j = 0; j < length; j += 3) {
var pickOffset = pickOffsets[pickIndex++];
var offset = pickOffset.offset;
var count = pickOffset.count;
var vertexArray = primitive._va[pickOffset.index];
// stencil preload command
var command = pickCommands[j];
if (!defined(command)) {
command = pickCommands[j] = new DrawCommand({
owner : groundPrimitive,
primitiveType : primitive._primitiveType
});
}
command.vertexArray = vertexArray;
command.offset = offset;
command.count = count;
command.renderState = groundPrimitive._rsStencilPreloadPass;
command.shaderProgram = groundPrimitive._sp;
command.uniformMap = uniformMap;
command.pass = Pass.GROUND;
// stencil depth command
command = pickCommands[j + 1];
if (!defined(command)) {
command = pickCommands[j + 1] = new DrawCommand({
owner : groundPrimitive,
primitiveType : primitive._primitiveType
});
}
command.vertexArray = vertexArray;
command.offset = offset;
command.count = count;
command.renderState = groundPrimitive._rsStencilDepthPass;
command.shaderProgram = groundPrimitive._sp;
command.uniformMap = uniformMap;
command.pass = Pass.GROUND;
// color command
command = pickCommands[j + 2];
if (!defined(command)) {
command = pickCommands[j + 2] = new DrawCommand({
owner : groundPrimitive,
primitiveType : primitive._primitiveType
});
}
command.vertexArray = vertexArray;
command.offset = offset;
command.count = count;
command.renderState = groundPrimitive._rsPickPass;
command.shaderProgram = groundPrimitive._spPick;
command.uniformMap = uniformMap;
command.pass = Pass.GROUND;
}
}
function createCommands(groundPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
createColorCommands(groundPrimitive, colorCommands);
createPickCommands(groundPrimitive, pickCommands);
}
function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
var boundingVolumes;
if (frameState.mode === SceneMode.SCENE3D) {
boundingVolumes = groundPrimitive._boundingVolumes;
} else if (frameState.mode !== SceneMode.SCENE3D && defined(groundPrimitive._boundingVolumes2D)) {
boundingVolumes = groundPrimitive._boundingVolumes2D;
}
var commandList = frameState.commandList;
var passes = frameState.passes;
if (passes.render) {
var colorLength = colorCommands.length;
for (var j = 0; j < colorLength; ++j) {
colorCommands[j].modelMatrix = modelMatrix;
colorCommands[j].boundingVolume = boundingVolumes[Math.floor(j / 3)];
colorCommands[j].cull = cull;
colorCommands[j].debugShowBoundingVolume = debugShowBoundingVolume;
commandList.push(colorCommands[j]);
}
}
if (passes.pick) {
var primitive = groundPrimitive._primitive;
var pickOffsets = primitive._pickOffsets;
var length = pickOffsets.length * 3;
pickCommands.length = length;
var pickIndex = 0;
for (var k = 0; k < length; k += 3) {
var pickOffset = pickOffsets[pickIndex++];
var bv = boundingVolumes[pickOffset.index];
pickCommands[k].modelMatrix = modelMatrix;
pickCommands[k].boundingVolume = bv;
pickCommands[k].cull = cull;
pickCommands[k + 1].modelMatrix = modelMatrix;
pickCommands[k + 1].boundingVolume = bv;
pickCommands[k + 1].cull = cull;
pickCommands[k + 2].modelMatrix = modelMatrix;
pickCommands[k + 2].boundingVolume = bv;
pickCommands[k + 2].cull = cull;
commandList.push(pickCommands[k], pickCommands[k + 1], pickCommands[k + 2]);
}
}
}
GroundPrimitive._initialized = false;
GroundPrimitive._initPromise = undefined;
/**
* Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the
* GroundPrimitive asynchronously.
*
* @returns {Promise} A promise that will resolve once the terrain heights have been loaded.
*
*/
GroundPrimitive.initializeTerrainHeights = function() {
var initPromise = GroundPrimitive._initPromise;
if (defined(initPromise)) {
return initPromise;
}
GroundPrimitive._initPromise = loadJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) {
GroundPrimitive._initialized = true;
GroundPrimitive._terrainHeights = json;
});
return GroundPrimitive._initPromise;
};
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {DeveloperError} All instance geometries must have the same primitiveType.
* @exception {DeveloperError} Appearance and material have a uniform with the same name.
* @exception {DeveloperError} Not all of the geometry instances have the same color attribute.
*/
GroundPrimitive.prototype.update = function(frameState) {
var context = frameState.context;
if (!context.fragmentDepth || !this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) {
return;
}
if (!GroundPrimitive._initialized) {
if (!this.asynchronous) {
throw new DeveloperError('For synchronous GroundPrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.');
}
GroundPrimitive.initializeTerrainHeights();
return;
}
if (!defined(this._primitive)) {
var primitiveOptions = this._primitiveOptions;
var ellipsoid = frameState.mapProjection.ellipsoid;
var instance;
var geometry;
var instanceType;
var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances];
var length = instances.length;
var groundInstances = new Array(length);
var i;
var color;
var rectangle;
for (i = 0; i < length; ++i) {
instance = instances[i];
geometry = instance.geometry;
var instanceRectangle = getRectangle(frameState, geometry);
if (!defined(rectangle)) {
rectangle = instanceRectangle;
} else {
if (defined(instanceRectangle)) {
Rectangle.union(rectangle, instanceRectangle, rectangle);
}
}
var id = instance.id;
if (defined(id) && defined(instanceRectangle)) {
var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid);
this._boundingSpheresKeys.push(id);
this._boundingSpheres.push(boundingSphere);
}
instanceType = geometry.constructor;
if (defined(instanceType) && defined(instanceType.createShadowVolume)) {
var attributes = instance.attributes;
if (!defined(attributes) || !defined(attributes.color)) {
throw new DeveloperError('Not all of the geometry instances have the same color attribute.');
} else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) {
throw new DeveloperError('Not all of the geometry instances have the same color attribute.');
} else if (!defined(color)) {
color = attributes.color;
}
} else {
throw new DeveloperError('Not all of the geometry instances have GroundPrimitive support.');
}
}
// Now compute the min/max heights for the primitive
setMinMaxTerrainHeights(this, rectangle, frameState.mapProjection.ellipsoid);
var exaggeration = frameState.terrainExaggeration;
this._minHeight = this._minTerrainHeight * exaggeration;
this._maxHeight = this._maxTerrainHeight * exaggeration;
for (i = 0; i < length; ++i) {
instance = instances[i];
geometry = instance.geometry;
instanceType = geometry.constructor;
groundInstances[i] = new GeometryInstance({
geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this),
getComputeMaximumHeightFunction(this)),
attributes : instance.attributes,
id : instance.id,
pickPrimitive : this
});
}
primitiveOptions.geometryInstances = groundInstances;
var that = this;
primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) {
createBoundingVolume(that, frameState, geometry);
};
primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) {
createRenderStates(that, context);
};
primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) {
createShaderProgram(that, frameState);
};
primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands);
};
primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses);
};
this._primitive = new Primitive(primitiveOptions);
this._primitive.readyPromise.then(function(primitive) {
that._ready = true;
if (that.releaseGeometryInstances) {
that.geometryInstances = undefined;
}
var error = primitive._error;
if (!defined(error)) {
that._readyPromise.resolve(that);
} else {
that._readyPromise.reject(error);
}
});
}
this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
this._primitive.update(frameState);
if (this.debugShowShadowVolume && !defined(this._debugPrimitive) && defined(this.geometryInstances)) {
var debugInstances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances];
var debugLength = debugInstances.length;
var debugVolumeInstances = new Array(debugLength);
for (var j = 0 ; j < debugLength; ++j) {
var debugInstance = debugInstances[j];
var debugGeometry = debugInstance.geometry;
var debugInstanceType = debugGeometry.constructor;
if (defined(debugInstanceType) && defined(debugInstanceType.createShadowVolume)) {
var debugColorArray = debugInstance.attributes.color.value;
var debugColor = Color.fromBytes(debugColorArray[0], debugColorArray[1], debugColorArray[2], debugColorArray[3]);
Color.subtract(new Color(1.0, 1.0, 1.0, 0.0), debugColor, debugColor);
debugVolumeInstances[j] = new GeometryInstance({
geometry : debugInstanceType.createShadowVolume(debugGeometry, getComputeMinimumHeightFunction(this), getComputeMaximumHeightFunction(this)),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(debugColor)
},
id : debugInstance.id,
pickPrimitive : this
});
}
}
this._debugPrimitive = new Primitive({
geometryInstances : debugVolumeInstances,
releaseGeometryInstances : true,
allowPicking : false,
asynchronous : false,
appearance : new PerInstanceColorAppearance({
flat : true
})
});
}
if (defined(this._debugPrimitive)) {
if (this.debugShowShadowVolume) {
this._debugPrimitive.update(frameState);
} else {
this._debugPrimitive.destroy();
this._debugPrimitive = undefined;
}
}
};
/**
* @private
*/
GroundPrimitive.prototype.getBoundingSphere = function(id) {
var index = this._boundingSpheresKeys.indexOf(id);
if (index !== -1) {
return this._boundingSpheres[index];
}
return undefined;
};
/**
* Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
*
* @param {Object} id The id of the {@link GeometryInstance}.
* @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
*
* @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
*
* @example
* var attributes = primitive.getGeometryInstanceAttributes('an id');
* attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
* attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
*/
GroundPrimitive.prototype.getGeometryInstanceAttributes = function(id) {
if (!defined(this._primitive)) {
throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
}
return this._primitive.getGeometryInstanceAttributes(id);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see GroundPrimitive#destroy
*/
GroundPrimitive.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* e = e && e.destroy();
*
* @see GroundPrimitive#isDestroyed
*/
GroundPrimitive.prototype.destroy = function() {
this._primitive = this._primitive && this._primitive.destroy();
this._debugPrimitive = this._debugPrimitive && this._debugPrimitive.destroy();
this._sp = this._sp && this._sp.destroy();
this._spPick = this._spPick && this._spPick.destroy();
return destroyObject(this);
};
return GroundPrimitive;
});
/*global define*/
define('DataSources/CorridorGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/CorridorGeometry',
'../Core/CorridorOutlineGeometry',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/oneTimeWarning',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/GroundPrimitive',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
CorridorGeometry,
CorridorOutlineGeometry,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
oneTimeWarning,
ShowGeometryInstanceAttribute,
GroundPrimitive,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.positions = undefined;
this.width = undefined;
this.cornerType = undefined;
this.height = undefined;
this.extrudedHeight = undefined;
this.granularity = undefined;
}
/**
* A {@link GeometryUpdater} for corridors.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias CorridorGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function CorridorGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(CorridorGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._isClosed = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._onTerrain = false;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'corridor', entity.corridor, undefined);
}
defineProperties(CorridorGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof CorridorGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof CorridorGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(CorridorGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayCondition;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
get : function() {
return this._isClosed;
}
},
/**
* Gets a value indicating if the geometry should be drawn on terrain.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
onTerrain : {
get : function() {
return this._onTerrain;
}
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof CorridorGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
CorridorGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
CorridorGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
CorridorGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(this._distanceDisplayCondition.getValue(time));
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayCondition,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayCondition
};
}
return new GeometryInstance({
id : entity,
geometry : new CorridorGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
CorridorGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
return new GeometryInstance({
id : entity,
geometry : new CorridorOutlineGeometry(this._options),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(this._distanceDisplayCondition.getValue(time))
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
CorridorGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
CorridorGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
CorridorGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'corridor')) {
return;
}
var corridor = this._entity.corridor;
if (!defined(corridor)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = corridor.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = corridor.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var positions = corridor.positions;
var show = corridor.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(positions))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(corridor.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(corridor.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(corridor.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(corridor.shadows, defaultShadows);
this._distanceDisplayCondition = defaultValue(corridor.distanceDisplayCondition, defaultDistanceDisplayCondition);
var height = corridor.height;
var extrudedHeight = corridor.extrudedHeight;
var granularity = corridor.granularity;
var width = corridor.width;
var outlineWidth = corridor.outlineWidth;
var cornerType = corridor.cornerType;
var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) &&
isColorMaterial && GroundPrimitive.isSupported(this._scene);
if (outlineEnabled && onTerrain) {
oneTimeWarning(oneTimeWarning.geometryOutlines);
outlineEnabled = false;
}
this._fillEnabled = fillEnabled;
this._onTerrain = onTerrain;
this._isClosed = defined(extrudedHeight) || onTerrain;
this._outlineEnabled = outlineEnabled;
if (!positions.isConstant || //
!Property.isConstant(height) || //
!Property.isConstant(extrudedHeight) || //
!Property.isConstant(granularity) || //
!Property.isConstant(width) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(cornerType) || //
(onTerrain && !Property.isConstant(material))) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.positions = positions.getValue(Iso8601.MINIMUM_VALUE, options.positions);
options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.cornerType = defined(cornerType) ? cornerType.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
CorridorGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
this._primitives = primitives;
this._groundPrimitives = groundPrimitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var geometryUpdater = this._geometryUpdater;
var onTerrain = geometryUpdater._onTerrain;
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._outlinePrimitive = undefined;
}
this._primitive = undefined;
var entity = geometryUpdater._entity;
var corridor = entity.corridor;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(corridor.show, time, true)) {
return;
}
var options = this._options;
var positions = Property.getValueOrUndefined(corridor.positions, time, options.positions);
var width = Property.getValueOrUndefined(corridor.width, time);
if (!defined(positions) || !defined(width)) {
return;
}
options.positions = positions;
options.width = width;
options.height = Property.getValueOrUndefined(corridor.height, time);
options.extrudedHeight = Property.getValueOrUndefined(corridor.extrudedHeight, time);
options.granularity = Property.getValueOrUndefined(corridor.granularity, time);
options.cornerType = Property.getValueOrUndefined(corridor.cornerType, time);
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
var distanceDisplayCondition = this._geometryUpdater.distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (!defined(corridor.fill) || corridor.fill.getValue(time)) {
var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
this._material = material;
if (onTerrain) {
var currentColor = Color.WHITE;
if (defined(fillMaterialProperty.color)) {
currentColor = fillMaterialProperty.color.getValue(time);
}
this._primitive = groundPrimitives.add(new GroundPrimitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new CorridorGeometry(options),
attributes: {
color: ColorGeometryInstanceAttribute.fromColor(currentColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
asynchronous : false,
shadows : shadows
}));
} else {
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : defined(options.extrudedHeight)
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new CorridorGeometry(options),
attributes : {
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
}
if (!onTerrain && defined(corridor.outline) && corridor.outline.getValue(time)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(corridor.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(corridor.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new CorridorOutlineGeometry(options),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (this._geometryUpdater._onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
}
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return CorridorGeometryUpdater;
});
/*global define*/
define('DataSources/DataSource',[
'../Core/defineProperties',
'../Core/DeveloperError'
], function(
defineProperties,
DeveloperError) {
'use strict';
/**
* Defines the interface for data sources, which turn arbitrary data into a
* {@link EntityCollection} for generic consumption. This object is an interface
* for documentation purposes and is not intended to be instantiated directly.
* @alias DataSource
* @constructor
*
* @see Entity
* @see DataSourceDisplay
*/
function DataSource() {
DeveloperError.throwInstantiationError();
}
defineProperties(DataSource.prototype, {
/**
* Gets a human-readable name for this instance.
* @memberof DataSource.prototype
* @type {String}
*/
name : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the preferred clock settings for this data source.
* @memberof DataSource.prototype
* @type {DataSourceClock}
*/
clock : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the collection of {@link Entity} instances.
* @memberof DataSource.prototype
* @type {EntityCollection}
*/
entities : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if the data source is currently loading data.
* @memberof DataSource.prototype
* @type {Boolean}
*/
isLoading : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets an event that will be raised when the underlying data changes.
* @memberof DataSource.prototype
* @type {Event}
*/
changedEvent : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets an event that will be raised if an error is encountered during processing.
* @memberof DataSource.prototype
* @type {Event}
*/
errorEvent : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets an event that will be raised when the value of isLoading changes.
* @memberof DataSource.prototype
* @type {Event}
*/
loadingEvent : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets whether or not this data source should be displayed.
* @memberof DataSource.prototype
* @type {Boolean}
*/
show : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
*
* @memberof DataSource.prototype
* @type {EntityCluster}
*/
clustering : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Updates the data source to the provided time. This function is optional and
* is not required to be implemented. It is provided for data sources which
* retrieve data based on the current animation time or scene state.
* If implemented, update will be called by {@link DataSourceDisplay} once a frame.
* @function
*
* @param {JulianDate} time The simulation time.
* @returns {Boolean} True if this data source is ready to be displayed at the provided time, false otherwise.
*/
DataSource.prototype.update = DeveloperError.throwInstantiationError;
/**
* @private
*/
DataSource.setLoading = function(dataSource, isLoading) {
if (dataSource._isLoading !== isLoading) {
if (isLoading) {
dataSource._entityCollection.suspendEvents();
} else {
dataSource._entityCollection.resumeEvents();
}
dataSource._isLoading = isLoading;
dataSource._loading.raiseEvent(dataSource, isLoading);
}
};
return DataSource;
});
/*global define*/
define('Scene/SceneTransforms',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Math',
'../Core/Matrix4',
'../Core/Transforms',
'./SceneMode'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
defined,
DeveloperError,
CesiumMath,
Matrix4,
Transforms,
SceneMode) {
'use strict';
/**
* Functions that do scene-dependent transforms between rendering-related coordinate systems.
*
* @exports SceneTransforms
*/
var SceneTransforms = {};
var actualPositionScratch = new Cartesian4(0, 0, 0, 1);
var positionCC = new Cartesian4();
var scratchViewport = new BoundingRectangle();
var scratchWindowCoord0 = new Cartesian2();
var scratchWindowCoord1 = new Cartesian2();
/**
* Transforms a position in WGS84 coordinates to window coordinates. This is commonly used to place an
* HTML element at the same screen position as an object in the scene.
*
* @param {Scene} scene The scene.
* @param {Cartesian3} position The position in WGS84 (world) coordinates.
* @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be undefined
if the input position is near the center of the ellipsoid.
*
* @example
* // Output the window position of longitude/latitude (0, 0) every time the mouse moves.
* var scene = widget.scene;
* var ellipsoid = scene.globe.ellipsoid;
* var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
* handler.setInputAction(function(movement) {
* console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position));
* }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
*/
SceneTransforms.wgs84ToWindowCoordinates = function(scene, position, result) {
return SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(scene, position, Cartesian3.ZERO, result);
};
var scratchCartesian4 = new Cartesian4();
var scratchEyeOffset = new Cartesian3();
function worldToClip(position, eyeOffset, camera, result) {
var viewMatrix = camera.viewMatrix;
var positionEC = Matrix4.multiplyByVector(viewMatrix, Cartesian4.fromElements(position.x, position.y, position.z, 1, scratchCartesian4), scratchCartesian4);
var zEyeOffset = Cartesian3.multiplyComponents(eyeOffset, Cartesian3.normalize(positionEC, scratchEyeOffset), scratchEyeOffset);
positionEC.x += eyeOffset.x + zEyeOffset.x;
positionEC.y += eyeOffset.y + zEyeOffset.y;
positionEC.z += zEyeOffset.z;
return Matrix4.multiplyByVector(camera.frustum.projectionMatrix, positionEC, result);
}
var scratchMaxCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
var scratchProjectedCartesian = new Cartesian3();
var scratchCameraPosition = new Cartesian3();
/**
* @private
*/
SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates = function(scene, position, eyeOffset, result) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
// Transform for 3D, 2D, or Columbus view
var frameState = scene.frameState;
var actualPosition = SceneTransforms.computeActualWgs84Position(frameState, position, actualPositionScratch);
if (!defined(actualPosition)) {
return undefined;
}
// Assuming viewport takes up the entire canvas...
var canvas = scene.canvas;
var viewport = scratchViewport;
viewport.x = 0;
viewport.y = 0;
viewport.width = canvas.clientWidth;
viewport.height = canvas.clientHeight;
var camera = scene.camera;
var cameraCentered = false;
if (frameState.mode === SceneMode.SCENE2D) {
var projection = scene.mapProjection;
var maxCartographic = scratchMaxCartographic;
var maxCoord = projection.project(maxCartographic, scratchProjectedCartesian);
var cameraPosition = Cartesian3.clone(camera.position, scratchCameraPosition);
var frustum = camera.frustum.clone();
var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, new Matrix4());
var projectionMatrix = camera.frustum.projectionMatrix;
var x = camera.positionWC.y;
var eyePoint = Cartesian3.fromElements(CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x);
var windowCoordinates = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, eyePoint);
if (x === 0.0 || windowCoordinates.x <= 0.0 || windowCoordinates.x >= canvas.clientWidth) {
cameraCentered = true;
} else {
if (windowCoordinates.x > canvas.clientWidth * 0.5) {
viewport.width = windowCoordinates.x;
camera.frustum.right = maxCoord.x - x;
positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord0);
viewport.x += windowCoordinates.x;
camera.position.x = -camera.position.x;
var right = camera.frustum.right;
camera.frustum.right = -camera.frustum.left;
camera.frustum.left = -right;
positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord1);
} else {
viewport.x += windowCoordinates.x;
viewport.width -= windowCoordinates.x;
camera.frustum.left = -maxCoord.x - x;
positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord0);
viewport.x = viewport.x - viewport.width;
camera.position.x = -camera.position.x;
var left = camera.frustum.left;
camera.frustum.left = -camera.frustum.right;
camera.frustum.right = -left;
positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord1);
}
Cartesian3.clone(cameraPosition, camera.position);
camera.frustum = frustum.clone();
result = Cartesian2.clone(scratchWindowCoord0, result);
if (result.x < 0.0 || result.x > canvas.clientWidth) {
result.x = scratchWindowCoord1.x;
}
}
}
if (frameState.mode !== SceneMode.SCENE2D || cameraCentered) {
// View-projection matrix to transform from world coordinates to clip coordinates
positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
if (positionCC.z < 0 && frameState.mode !== SceneMode.SCENE2D) {
return undefined;
}
result = SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, result);
}
result.y = canvas.clientHeight - result.y;
return result;
};
/**
* Transforms a position in WGS84 coordinates to drawing buffer coordinates. This may produce different
* results from SceneTransforms.wgs84ToWindowCoordinates when the browser zoom is not 100%, or on high-DPI displays.
*
* @param {Scene} scene The scene.
* @param {Cartesian3} position The position in WGS84 (world) coordinates.
* @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates.
* @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. This may be undefined
if the input position is near the center of the ellipsoid.
*
* @example
* // Output the window position of longitude/latitude (0, 0) every time the mouse moves.
* var scene = widget.scene;
* var ellipsoid = scene.globe.ellipsoid;
* var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
* var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
* handler.setInputAction(function(movement) {
* console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position));
* }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
*/
SceneTransforms.wgs84ToDrawingBufferCoordinates = function(scene, position, result) {
result = SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
if (!defined(result)) {
return undefined;
}
return SceneTransforms.transformWindowToDrawingBuffer(scene, result, result);
};
var projectedPosition = new Cartesian3();
var positionInCartographic = new Cartographic();
/**
* @private
*/
SceneTransforms.computeActualWgs84Position = function(frameState, position, result) {
var mode = frameState.mode;
if (mode === SceneMode.SCENE3D) {
return Cartesian3.clone(position, result);
}
var projection = frameState.mapProjection;
var cartographic = projection.ellipsoid.cartesianToCartographic(position, positionInCartographic);
if (!defined(cartographic)) {
return undefined;
}
projection.project(cartographic, projectedPosition);
if (mode === SceneMode.COLUMBUS_VIEW) {
return Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, result);
}
if (mode === SceneMode.SCENE2D) {
return Cartesian3.fromElements(0.0, projectedPosition.x, projectedPosition.y, result);
}
// mode === SceneMode.MORPHING
var morphTime = frameState.morphTime;
return Cartesian3.fromElements(
CesiumMath.lerp(projectedPosition.z, position.x, morphTime),
CesiumMath.lerp(projectedPosition.x, position.y, morphTime),
CesiumMath.lerp(projectedPosition.y, position.z, morphTime),
result);
};
var positionNDC = new Cartesian3();
var positionWC = new Cartesian3();
var viewportTransform = new Matrix4();
/**
* @private
*/
SceneTransforms.clipToGLWindowCoordinates = function(viewport, position, result) {
// Perspective divide to transform from clip coordinates to normalized device coordinates
Cartesian3.divideByScalar(position, position.w, positionNDC);
// Viewport transform to transform from clip coordinates to window coordinates
Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform);
Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC);
return Cartesian2.fromCartesian3(positionWC, result);
};
/**
* @private
*/
SceneTransforms.clipToDrawingBufferCoordinates = function(viewport, position, result) {
// Perspective divide to transform from clip coordinates to normalized device coordinates
Cartesian3.divideByScalar(position, position.w, positionNDC);
// Viewport transform to transform from clip coordinates to drawing buffer coordinates
Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform);
Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC);
return Cartesian2.fromCartesian3(positionWC, result);
};
/**
* @private
*/
SceneTransforms.transformWindowToDrawingBuffer = function(scene, windowPosition, result) {
var canvas = scene.canvas;
var xScale = scene.drawingBufferWidth / canvas.clientWidth;
var yScale = scene.drawingBufferHeight / canvas.clientHeight;
return Cartesian2.fromElements(windowPosition.x * xScale, windowPosition.y * yScale, result);
};
var scratchNDC = new Cartesian4();
var scratchWorldCoords = new Cartesian4();
/**
* @private
*/
SceneTransforms.drawingBufferToWgs84Coordinates = function(scene, drawingBufferPosition, depth, result) {
var context = scene.context;
var uniformState = context.uniformState;
var viewport = scene._passState.viewport;
var ndc = Cartesian4.clone(Cartesian4.UNIT_W, scratchNDC);
ndc.x = (drawingBufferPosition.x - viewport.x) / viewport.width * 2.0 - 1.0;
ndc.y = (drawingBufferPosition.y - viewport.y) / viewport.height * 2.0 - 1.0;
ndc.z = (depth * 2.0) - 1.0;
ndc.w = 1.0;
var worldCoords = Matrix4.multiplyByVector(uniformState.inverseViewProjection, ndc, scratchWorldCoords);
// Reverse perspective divide
var w = 1.0 / worldCoords.w;
Cartesian3.multiplyByScalar(worldCoords, w, worldCoords);
return Cartesian3.fromCartesian4(worldCoords, result);
};
return SceneTransforms;
});
/*global define*/
define('Scene/Billboard',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/Color',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/Matrix4',
'../Core/NearFarScalar',
'./HeightReference',
'./HorizontalOrigin',
'./SceneMode',
'./SceneTransforms',
'./VerticalOrigin'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
Color,
createGuid,
defaultValue,
defined,
defineProperties,
DeveloperError,
DistanceDisplayCondition,
Matrix4,
NearFarScalar,
HeightReference,
HorizontalOrigin,
SceneMode,
SceneTransforms,
VerticalOrigin) {
'use strict';
/**
* A viewport-aligned image positioned in the 3D scene, that is created
* and rendered using a {@link BillboardCollection}. A billboard is created and its initial
* properties are set by calling {@link BillboardCollection#add}.
*
*
*
* Example billboards
*
*
* @alias Billboard
*
* @performance Reading a property, e.g., {@link Billboard#show}, is constant time.
* Assigning to a property is constant time but results in
* CPU to GPU traffic when {@link BillboardCollection#update} is called. The per-billboard traffic is
* the same regardless of how many properties were updated. If most billboards in a collection need to be
* updated, it may be more efficient to clear the collection with {@link BillboardCollection#removeAll}
* and add new billboards instead of modifying each one.
*
* @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near
* @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
* @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near
* @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
*
* @see BillboardCollection
* @see BillboardCollection#add
* @see Label
*
* @internalConstructor
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
*/
function Billboard(options, billboardCollection) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) {
throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.');
}
if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) {
throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.');
}
if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) {
throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.');
}
if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) {
throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near');
}
this._show = defaultValue(options.show, true);
this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D
this._pixelOffset = Cartesian2.clone(defaultValue(options.pixelOffset, Cartesian2.ZERO));
this._translate = new Cartesian2(0.0, 0.0); // used by labels for glyph vertex translation
this._eyeOffset = Cartesian3.clone(defaultValue(options.eyeOffset, Cartesian3.ZERO));
this._heightReference = defaultValue(options.heightReference, HeightReference.NONE);
this._verticalOrigin = defaultValue(options.verticalOrigin, VerticalOrigin.CENTER);
this._horizontalOrigin = defaultValue(options.horizontalOrigin, HorizontalOrigin.CENTER);
this._scale = defaultValue(options.scale, 1.0);
this._color = Color.clone(defaultValue(options.color, Color.WHITE));
this._rotation = defaultValue(options.rotation, 0.0);
this._alignedAxis = Cartesian3.clone(defaultValue(options.alignedAxis, Cartesian3.ZERO));
this._width = options.width;
this._height = options.height;
this._scaleByDistance = options.scaleByDistance;
this._translucencyByDistance = options.translucencyByDistance;
this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance;
this._sizeInMeters = defaultValue(options.sizeInMeters, false);
this._distanceDisplayCondition = options.distanceDisplayCondition;
this._id = options.id;
this._collection = defaultValue(options.collection, billboardCollection);
this._pickId = undefined;
this._pickPrimitive = defaultValue(options._pickPrimitive, this);
this._billboardCollection = billboardCollection;
this._dirty = false;
this._index = -1; //Used only by BillboardCollection
this._imageIndex = -1;
this._imageIndexPromise = undefined;
this._imageId = undefined;
this._image = undefined;
this._imageSubRegion = undefined;
this._imageWidth = undefined;
this._imageHeight = undefined;
var image = options.image;
var imageId = options.imageId;
if (defined(image)) {
if (!defined(imageId)) {
if (typeof image === 'string') {
imageId = image;
} else if (defined(image.src)) {
imageId = image.src;
} else {
imageId = createGuid();
}
}
this._imageId = imageId;
this._image = image;
}
if (defined(options.imageSubRegion)) {
this._imageId = imageId;
this._imageSubRegion = options.imageSubRegion;
}
if (defined(this._billboardCollection._textureAtlas)) {
this._loadImage();
}
this._actualClampedPosition = undefined;
this._removeCallbackFunc = undefined;
this._mode = SceneMode.SCENE3D;
this._clusterShow = true;
this._updateClamping();
}
var SHOW_INDEX = Billboard.SHOW_INDEX = 0;
var POSITION_INDEX = Billboard.POSITION_INDEX = 1;
var PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX = 2;
var EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX = 3;
var HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX = 4;
var VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX = 5;
var SCALE_INDEX = Billboard.SCALE_INDEX = 6;
var IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX = 7;
var COLOR_INDEX = Billboard.COLOR_INDEX = 8;
var ROTATION_INDEX = Billboard.ROTATION_INDEX = 9;
var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX = 10;
var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX = 11;
var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX = 12;
var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = 13;
var DISTANCE_DISPLAY_CONDITION = Billboard.DISTANCE_DISPLAY_CONDITION = 14;
Billboard.NUMBER_OF_PROPERTIES = 15;
function makeDirty(billboard, propertyChanged) {
var billboardCollection = billboard._billboardCollection;
if (defined(billboardCollection)) {
billboardCollection._updateBillboard(billboard, propertyChanged);
billboard._dirty = true;
}
}
defineProperties(Billboard.prototype, {
/**
* Determines if this billboard will be shown. Use this to hide or show a billboard, instead
* of removing it and re-adding it to the collection.
* @memberof Billboard.prototype
* @type {Boolean}
* @default true
*/
show : {
get : function() {
return this._show;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._show !== value) {
this._show = value;
makeDirty(this, SHOW_INDEX);
}
}
},
/**
* Gets or sets the Cartesian position of this billboard.
* @memberof Billboard.prototype
* @type {Cartesian3}
*/
position : {
get : function() {
return this._position;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var position = this._position;
if (!Cartesian3.equals(position, value)) {
Cartesian3.clone(value, position);
Cartesian3.clone(value, this._actualPosition);
this._updateClamping();
makeDirty(this, POSITION_INDEX);
}
}
},
/**
* Gets or sets the height reference of this billboard.
* @memberof Billboard.prototype
* @type {HeightReference}
* @default HeightReference.NONE
*/
heightReference : {
get : function() {
return this._heightReference;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var heightReference = this._heightReference;
if (value !== heightReference) {
this._heightReference = value;
this._updateClamping();
makeDirty(this, POSITION_INDEX);
}
}
},
/**
* Gets or sets the pixel offset in screen space from the origin of this billboard. This is commonly used
* to align multiple billboards and labels at the same position, e.g., an image and text. The
* screen space origin is the top, left corner of the canvas; x
increases from
* left to right, and y
increases from top to bottom.
*
*
*
* default
* b.pixeloffset = new Cartesian2(50, 25);
*
* The billboard's origin is indicated by the yellow point.
*
* @memberof Billboard.prototype
* @type {Cartesian2}
*/
pixelOffset : {
get : function() {
return this._pixelOffset;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var pixelOffset = this._pixelOffset;
if (!Cartesian2.equals(pixelOffset, value)) {
Cartesian2.clone(value, pixelOffset);
makeDirty(this, PIXEL_OFFSET_INDEX);
}
}
},
/**
* Gets or sets near and far scaling properties of a Billboard based on the billboard's distance from the camera.
* A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the billboard's scale remains clamped to the nearest bound. If undefined,
* scaleByDistance will be disabled.
* @memberof Billboard.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a billboard's scaleByDistance to scale by 1.5 when the
* // camera is 1500 meters from the billboard and disappear as
* // the camera distance approaches 8.0e6 meters.
* b.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0);
*
* @example
* // Example 2.
* // disable scaling by distance
* b.scaleByDistance = undefined;
*/
scaleByDistance : {
get : function() {
return this._scaleByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var scaleByDistance = this._scaleByDistance;
if (!NearFarScalar.equals(scaleByDistance, value)) {
this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);
makeDirty(this, SCALE_BY_DISTANCE_INDEX);
}
}
},
/**
* Gets or sets near and far translucency properties of a Billboard based on the billboard's distance from the camera.
* A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the billboard's translucency remains clamped to the nearest bound. If undefined,
* translucencyByDistance will be disabled.
* @memberof Billboard.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a billboard's translucency to 1.0 when the
* // camera is 1500 meters from the billboard and disappear as
* // the camera distance approaches 8.0e6 meters.
* b.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
*
* @example
* // Example 2.
* // disable translucency by distance
* b.translucencyByDistance = undefined;
*/
translucencyByDistance : {
get : function() {
return this._translucencyByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var translucencyByDistance = this._translucencyByDistance;
if (!NearFarScalar.equals(translucencyByDistance, value)) {
this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance);
makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX);
}
}
},
/**
* Gets or sets near and far pixel offset scaling properties of a Billboard based on the billboard's distance from the camera.
* A billboard's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the billboard's pixel offset scale remains clamped to the nearest bound. If undefined,
* pixelOffsetScaleByDistance will be disabled.
* @memberof Billboard.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a billboard's pixel offset scale to 0.0 when the
* // camera is 1500 meters from the billboard and scale pixel offset to 10.0 pixels
* // in the y direction the camera distance approaches 8.0e6 meters.
* b.pixelOffset = new Cesium.Cartesian2(0.0, 1.0);
* b.pixelOffsetScaleByDistance = new Cesium.NearFarScalar(1.5e2, 0.0, 8.0e6, 10.0);
*
* @example
* // Example 2.
* // disable pixel offset by distance
* b.pixelOffsetScaleByDistance = undefined;
*/
pixelOffsetScaleByDistance : {
get : function() {
return this._pixelOffsetScaleByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) {
this._pixelOffsetScaleByDistance = NearFarScalar.clone(value, pixelOffsetScaleByDistance);
makeDirty(this, PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX);
}
}
},
/**
* Gets or sets the 3D Cartesian offset applied to this billboard in eye coordinates. Eye coordinates is a left-handed
* coordinate system, where x
points towards the viewer's right, y
points up, and
* z
points into the screen. Eye coordinates use the same scale as world and model coordinates,
* which is typically meters.
*
* An eye offset is commonly used to arrange multiple billboards or objects at the same position, e.g., to
* arrange a billboard above its corresponding 3D model.
*
* Below, the billboard is positioned at the center of the Earth but an eye offset makes it always
* appear on top of the Earth regardless of the viewer's or Earth's orientation.
*
*
*
*
*
*
*
b.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);
*
* @memberof Billboard.prototype
* @type {Cartesian3}
*/
eyeOffset : {
get : function() {
return this._eyeOffset;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var eyeOffset = this._eyeOffset;
if (!Cartesian3.equals(eyeOffset, value)) {
Cartesian3.clone(value, eyeOffset);
makeDirty(this, EYE_OFFSET_INDEX);
}
}
},
/**
* Gets or sets the horizontal origin of this billboard, which determines if the billboard is
* to the left, center, or right of its anchor position.
*
*
*
*
* @memberof Billboard.prototype
* @type {HorizontalOrigin}
* @example
* // Use a bottom, left origin
* b.horizontalOrigin = Cesium.HorizontalOrigin.LEFT;
* b.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
*/
horizontalOrigin : {
get : function() {
return this._horizontalOrigin;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._horizontalOrigin !== value) {
this._horizontalOrigin = value;
makeDirty(this, HORIZONTAL_ORIGIN_INDEX);
}
}
},
/**
* Gets or sets the vertical origin of this billboard, which determines if the billboard is
* to the above, below, or at the center of its anchor position.
*
*
*
*
* @memberof Billboard.prototype
* @type {VerticalOrigin}
* @example
* // Use a bottom, left origin
* b.horizontalOrigin = Cesium.HorizontalOrigin.LEFT;
* b.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
*/
verticalOrigin : {
get : function() {
return this._verticalOrigin;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._verticalOrigin !== value) {
this._verticalOrigin = value;
makeDirty(this, VERTICAL_ORIGIN_INDEX);
}
}
},
/**
* Gets or sets the uniform scale that is multiplied with the billboard's image size in pixels.
* A scale of 1.0
does not change the size of the billboard; a scale greater than
* 1.0
enlarges the billboard; a positive scale less than 1.0
shrinks
* the billboard.
*
*
*
* From left to right in the above image, the scales are
0.5
,
1.0
,
* and
2.0
.
*
* @memberof Billboard.prototype
* @type {Number}
*/
scale : {
get : function() {
return this._scale;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._scale !== value) {
this._scale = value;
makeDirty(this, SCALE_INDEX);
}
}
},
/**
* Gets or sets the color that is multiplied with the billboard's texture. This has two common use cases. First,
* the same white texture may be used by many different billboards, each with a different color, to create
* colored billboards. Second, the color's alpha component can be used to make the billboard translucent as shown below.
* An alpha of 0.0
makes the billboard transparent, and 1.0
makes the billboard opaque.
*
*
*
* default
* alpha : 0.5
*
*
*
* The red, green, blue, and alpha values are indicated by value
's red
, green
,
* blue
, and alpha
properties as shown in Example 1. These components range from 0.0
* (no intensity) to 1.0
(full intensity).
* @memberof Billboard.prototype
* @type {Color}
*
* @example
* // Example 1. Assign yellow.
* b.color = Cesium.Color.YELLOW;
*
* @example
* // Example 2. Make a billboard 50% translucent.
* b.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5);
*/
color : {
get : function() {
return this._color;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var color = this._color;
if (!Color.equals(color, value)) {
Color.clone(value, color);
makeDirty(this, COLOR_INDEX);
}
}
},
/**
* Gets or sets the rotation angle in radians.
* @memberof Billboard.prototype
* @type {Number}
*/
rotation : {
get : function() {
return this._rotation;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._rotation !== value) {
this._rotation = value;
makeDirty(this, ROTATION_INDEX);
}
}
},
/**
* Gets or sets the aligned axis in world space. The aligned axis is the unit vector that the billboard up vector points towards.
* The default is the zero vector, which means the billboard is aligned to the screen up vector.
* @memberof Billboard.prototype
* @type {Cartesian3}
* @example
* // Example 1.
* // Have the billboard up vector point north
* billboard.alignedAxis = Cesium.Cartesian3.UNIT_Z;
*
* @example
* // Example 2.
* // Have the billboard point east.
* billboard.alignedAxis = Cesium.Cartesian3.UNIT_Z;
* billboard.rotation = -Cesium.Math.PI_OVER_TWO;
*
* @example
* // Example 3.
* // Reset the aligned axis
* billboard.alignedAxis = Cesium.Cartesian3.ZERO;
*/
alignedAxis : {
get : function() {
return this._alignedAxis;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var alignedAxis = this._alignedAxis;
if (!Cartesian3.equals(alignedAxis, value)) {
Cartesian3.clone(value, alignedAxis);
makeDirty(this, ALIGNED_AXIS_INDEX);
}
}
},
/**
* Gets or sets a width for the billboard. If undefined, the image width will be used.
* @memberof Billboard.prototype
* @type {Number}
*/
width : {
get : function() {
return defaultValue(this._width, this._imageWidth);
},
set : function(value) {
if (this._width !== value) {
this._width = value;
makeDirty(this, IMAGE_INDEX_INDEX);
}
}
},
/**
* Gets or sets a height for the billboard. If undefined, the image height will be used.
* @memberof Billboard.prototype
* @type {Number}
*/
height : {
get : function() {
return defaultValue(this._height, this._imageHeight);
},
set : function(value) {
if (this._height !== value) {
this._height = value;
makeDirty(this, IMAGE_INDEX_INDEX);
}
}
},
/**
* Gets or sets if the billboard size is in meters or pixels. true
to size the billboard in meters;
* otherwise, the size is in pixels.
* @memberof Billboard.prototype
* @type {Boolean}
* @default false
*/
sizeInMeters : {
get : function() {
return this._sizeInMeters;
},
set : function(value) {
if (this._sizeInMeters !== value) {
this._sizeInMeters = value;
makeDirty(this, COLOR_INDEX);
}
}
},
/**
* Gets or sets the condition specifying at what distance from the camera that this billboard will be displayed.
* @memberof Billboard.prototype
* @type {DistanceDisplayCondition}
* @default undefined
*/
distanceDisplayCondition : {
get : function() {
return this._distanceDisplayCondition;
},
set : function(value) {
if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
makeDirty(this, DISTANCE_DISPLAY_CONDITION);
}
}
},
/**
* Gets or sets the user-defined object returned when the billboard is picked.
* @memberof Billboard.prototype
* @type {Object}
*/
id : {
get : function() {
return this._id;
},
set : function(value) {
this._id = value;
if (defined(this._pickId)) {
this._pickId.object.id = value;
}
}
},
/**
* The primitive to return when picking this billboard.
* @memberof Billboard.prototype
* @private
*/
pickPrimitive : {
get : function() {
return this._pickPrimitive;
},
set : function(value) {
this._pickPrimitive = value;
if (defined(this._pickId)) {
this._pickId.object.primitive = value;
}
}
},
/**
*
* Gets or sets the image to be used for this billboard. If a texture has already been created for the
* given image, the existing texture is used.
*
*
* This property can be set to a loaded Image, a URL which will be loaded as an Image automatically,
* a canvas, or another billboard's image property (from the same billboard collection).
*
*
* @memberof Billboard.prototype
* @type {String}
* @example
* // load an image from a URL
* b.image = 'some/image/url.png';
*
* // assuming b1 and b2 are billboards in the same billboard collection,
* // use the same image for both billboards.
* b2.image = b1.image;
*/
image : {
get : function() {
return this._imageId;
},
set : function(value) {
if (!defined(value)) {
this._imageIndex = -1;
this._imageSubRegion = undefined;
this._imageId = undefined;
this._image = undefined;
this._imageIndexPromise = undefined;
makeDirty(this, IMAGE_INDEX_INDEX);
} else if (typeof value === 'string') {
this.setImage(value, value);
} else if (defined(value.src)) {
this.setImage(value.src, value);
} else {
this.setImage(createGuid(), value);
}
}
},
/**
* When true
, this billboard is ready to render, i.e., the image
* has been downloaded and the WebGL resources are created.
*
* @memberof Billboard.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
ready : {
get : function() {
return this._imageIndex !== -1;
}
},
/**
* Keeps track of the position of the billboard based on the height reference.
* @memberof Billboard.prototype
* @type {Cartesian3}
* @private
*/
_clampedPosition : {
get : function() {
return this._actualClampedPosition;
},
set : function(value) {
this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition);
makeDirty(this, POSITION_INDEX);
}
},
/**
* Determines whether or not this billboard will be shown or hidden because it was clustered.
* @memberof Billboard.prototype
* @type {Boolean}
* @private
*/
clusterShow : {
get : function() {
return this._clusterShow;
},
set : function(value) {
if (this._clusterShow !== value) {
this._clusterShow = value;
makeDirty(this, SHOW_INDEX);
}
}
}
});
Billboard.prototype.getPickId = function(context) {
if (!defined(this._pickId)) {
this._pickId = context.createPickId({
primitive : this._pickPrimitive,
collection : this._collection,
id : this._id
});
}
return this._pickId;
};
Billboard.prototype._updateClamping = function() {
Billboard._updateClamping(this._billboardCollection, this);
};
var scratchCartographic = new Cartographic();
var scratchPosition = new Cartesian3();
Billboard._updateClamping = function(collection, owner) {
var scene = collection._scene;
if (!defined(scene)) {
if (owner._heightReference !== HeightReference.NONE) {
throw new DeveloperError('Height reference is not supported without a scene.');
}
return;
}
var globe = scene.globe;
var ellipsoid = globe.ellipsoid;
var surface = globe._surface;
var mode = scene.frameState.mode;
var modeChanged = mode !== owner._mode;
owner._mode = mode;
if ((owner._heightReference === HeightReference.NONE || modeChanged) && defined(owner._removeCallbackFunc)) {
owner._removeCallbackFunc();
owner._removeCallbackFunc = undefined;
owner._clampedPosition = undefined;
}
if (owner._heightReference === HeightReference.NONE || !defined(owner._position)) {
return;
}
var position = ellipsoid.cartesianToCartographic(owner._position);
if (!defined(position)) {
return;
}
if (defined(owner._removeCallbackFunc)) {
owner._removeCallbackFunc();
}
function updateFunction(clampedPosition) {
if (owner._heightReference === HeightReference.RELATIVE_TO_GROUND) {
if (owner._mode === SceneMode.SCENE3D) {
var clampedCart = ellipsoid.cartesianToCartographic(clampedPosition, scratchCartographic);
clampedCart.height += position.height;
ellipsoid.cartographicToCartesian(clampedCart, clampedPosition);
} else {
clampedPosition.x += position.height;
}
}
owner._clampedPosition = Cartesian3.clone(clampedPosition, owner._clampedPosition);
}
owner._removeCallbackFunc = surface.updateHeight(position, updateFunction);
Cartographic.clone(position, scratchCartographic);
var height = globe.getHeight(position);
if (defined(height)) {
scratchCartographic.height = height;
}
ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition);
updateFunction(scratchPosition);
};
Billboard.prototype._loadImage = function() {
var atlas = this._billboardCollection._textureAtlas;
var imageId = this._imageId;
var image = this._image;
var imageSubRegion = this._imageSubRegion;
var imageIndexPromise;
if (defined(image)) {
imageIndexPromise = atlas.addImage(imageId, image);
}
if (defined(imageSubRegion)) {
imageIndexPromise = atlas.addSubRegion(imageId, imageSubRegion);
}
this._imageIndexPromise = imageIndexPromise;
if (!defined(imageIndexPromise)) {
return;
}
var that = this;
imageIndexPromise.then(function(index) {
if (that._imageId !== imageId || that._image !== image || !BoundingRectangle.equals(that._imageSubRegion, imageSubRegion)) {
// another load occurred before this one finished, ignore the index
return;
}
// fill in imageWidth and imageHeight
var textureCoordinates = atlas.textureCoordinates[index];
that._imageWidth = atlas.texture.width * textureCoordinates.width;
that._imageHeight = atlas.texture.height * textureCoordinates.height;
that._imageIndex = index;
that._ready = true;
that._image = undefined;
that._imageIndexPromise = undefined;
makeDirty(that, IMAGE_INDEX_INDEX);
}).otherwise(function(error) {
console.error('Error loading image for billboard: ' + error);
that._imageIndexPromise = undefined;
});
};
/**
*
* Sets the image to be used for this billboard. If a texture has already been created for the
* given id, the existing texture is used.
*
*
* This function is useful for dynamically creating textures that are shared across many billboards.
* Only the first billboard will actually call the function and create the texture, while subsequent
* billboards created with the same id will simply re-use the existing texture.
*
*
* To load an image from a URL, setting the {@link Billboard#image} property is more convenient.
*
*
* @param {String} id The id of the image. This can be any string that uniquely identifies the image.
* @param {Image|Canvas|String|Billboard~CreateImageCallback} image The image to load. This parameter
* can either be a loaded Image or Canvas, a URL which will be loaded as an Image automatically,
* or a function which will be called to create the image if it hasn't been loaded already.
* @example
* // create a billboard image dynamically
* function drawImage(id) {
* // create and draw an image using a canvas
* var canvas = document.createElement('canvas');
* var context2D = canvas.getContext('2d');
* // ... draw image
* return canvas;
* }
* // drawImage will be called to create the texture
* b.setImage('myImage', drawImage);
*
* // subsequent billboards created in the same collection using the same id will use the existing
* // texture, without the need to create the canvas or draw the image
* b2.setImage('myImage', drawImage);
*/
Billboard.prototype.setImage = function(id, image) {
if (!defined(id)) {
throw new DeveloperError('id is required.');
}
if (!defined(image)) {
throw new DeveloperError('image is required.');
}
if (this._imageId === id) {
return;
}
this._imageIndex = -1;
this._imageSubRegion = undefined;
this._imageId = id;
this._image = image;
if (defined(this._billboardCollection._textureAtlas)) {
this._loadImage();
}
};
/**
* Uses a sub-region of the image with the given id as the image for this billboard,
* measured in pixels from the bottom-left.
*
* @param {String} id The id of the image to use.
* @param {BoundingRectangle} subRegion The sub-region of the image.
*
* @exception {RuntimeError} image with id must be in the atlas
*/
Billboard.prototype.setImageSubRegion = function(id, subRegion) {
if (!defined(id)) {
throw new DeveloperError('id is required.');
}
if (!defined(subRegion)) {
throw new DeveloperError('subRegion is required.');
}
if (this._imageId === id && BoundingRectangle.equals(this._imageSubRegion, subRegion)) {
return;
}
this._imageIndex = -1;
this._imageId = id;
this._imageSubRegion = BoundingRectangle.clone(subRegion);
if (defined(this._billboardCollection._textureAtlas)) {
this._loadImage();
}
};
Billboard.prototype._setTranslate = function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var translate = this._translate;
if (!Cartesian2.equals(translate, value)) {
Cartesian2.clone(value, translate);
makeDirty(this, PIXEL_OFFSET_INDEX);
}
};
Billboard.prototype._getActualPosition = function() {
return defined(this._clampedPosition) ? this._clampedPosition : this._actualPosition;
};
Billboard.prototype._setActualPosition = function(value) {
if (!(defined(this._clampedPosition))) {
Cartesian3.clone(value, this._actualPosition);
}
makeDirty(this, POSITION_INDEX);
};
var tempCartesian3 = new Cartesian4();
Billboard._computeActualPosition = function(billboard, position, frameState, modelMatrix) {
if (defined(billboard._clampedPosition)) {
if (frameState.mode !== billboard._mode) {
billboard._updateClamping();
}
return billboard._clampedPosition;
} else if (frameState.mode === SceneMode.SCENE3D) {
return position;
}
Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3);
return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3);
};
var scratchCartesian3 = new Cartesian3();
// This function is basically a stripped-down JavaScript version of BillboardCollectionVS.glsl
Billboard._computeScreenSpacePosition = function(modelMatrix, position, eyeOffset, pixelOffset, scene, result) {
// Model to world coordinates
var positionWorld = Matrix4.multiplyByPoint(modelMatrix, position, scratchCartesian3);
// World to window coordinates
var positionWC = SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(scene, positionWorld, eyeOffset, result);
if (!defined(positionWC)) {
return undefined;
}
// Apply pixel offset
Cartesian2.add(positionWC, pixelOffset, positionWC);
return positionWC;
};
var scratchPixelOffset = new Cartesian2(0.0, 0.0);
/**
* Computes the screen-space position of the billboard's origin, taking into account eye and pixel offsets.
* The screen space origin is the top, left corner of the canvas; x
increases from
* left to right, and y
increases from top to bottom.
*
* @param {Scene} scene The scene.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The screen-space position of the billboard.
*
* @exception {DeveloperError} Billboard must be in a collection.
*
* @example
* console.log(b.computeScreenSpacePosition(scene).toString());
*
* @see Billboard#eyeOffset
* @see Billboard#pixelOffset
*/
Billboard.prototype.computeScreenSpacePosition = function(scene, result) {
var billboardCollection = this._billboardCollection;
if (!defined(result)) {
result = new Cartesian2();
}
if (!defined(billboardCollection)) {
throw new DeveloperError('Billboard must be in a collection. Was it removed?');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
// pixel offset for screenspace computation is the pixelOffset + screenspace translate
Cartesian2.clone(this._pixelOffset, scratchPixelOffset);
Cartesian2.add(scratchPixelOffset, this._translate, scratchPixelOffset);
var modelMatrix = billboardCollection.modelMatrix;
var actualPosition = this._getActualPosition();
var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, actualPosition,
this._eyeOffset, scratchPixelOffset, scene, result);
return windowCoordinates;
};
/**
* Gets a billboard's screen space bounding box centered around screenSpacePosition.
* @param {Billboard} billboard The billboard to get the screen space bounding box for.
* @param {Cartesian2} screenSpacePosition The screen space center of the label.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The screen space bounding box.
*
* @private
*/
Billboard.getScreenSpaceBoundingBox = function(billboard, screenSpacePosition, result) {
var width = billboard.width;
var height = billboard.height;
var scale = billboard.scale;
width *= scale;
height *= scale;
var x = screenSpacePosition.x;
if (billboard.horizontalOrigin === HorizontalOrigin.RIGHT) {
x -= width;
} else if (billboard.horizontalOrigin === HorizontalOrigin.CENTER) {
x -= width * 0.5;
}
var y = screenSpacePosition.y;
if (billboard.verticalOrigin === VerticalOrigin.BOTTOM || billboard.verticalOrigin === VerticalOrigin.BASELINE) {
y -= height;
} else if (billboard.verticalOrigin === VerticalOrigin.CENTER) {
y -= height * 0.5;
}
if (!defined(result)) {
result = new BoundingRectangle();
}
result.x = x;
result.y = y;
result.width = width;
result.height = height;
return result;
};
/**
* Determines if this billboard equals another billboard. Billboards are equal if all their properties
* are equal. Billboards in different collections can be equal.
*
* @param {Billboard} other The billboard to compare for equality.
* @returns {Boolean} true
if the billboards are equal; otherwise, false
.
*/
Billboard.prototype.equals = function(other) {
return this === other ||
defined(other) &&
this._id === other._id &&
Cartesian3.equals(this._position, other._position) &&
this._imageId === other._imageId &&
this._show === other._show &&
this._scale === other._scale &&
this._verticalOrigin === other._verticalOrigin &&
this._horizontalOrigin === other._horizontalOrigin &&
this._heightReference === other._heightReference &&
BoundingRectangle.equals(this._imageSubRegion, other._imageSubRegion) &&
Color.equals(this._color, other._color) &&
Cartesian2.equals(this._pixelOffset, other._pixelOffset) &&
Cartesian2.equals(this._translate, other._translate) &&
Cartesian3.equals(this._eyeOffset, other._eyeOffset) &&
NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) &&
NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance) &&
DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition);
};
Billboard.prototype._destroy = function() {
if (defined(this._customData)) {
this._billboardCollection._scene.globe._surface.removeTileCustomData(this._customData);
this._customData = undefined;
}
if (defined(this._removeCallbackFunc)) {
this._removeCallbackFunc();
this._removeCallbackFunc = undefined;
}
this.image = undefined;
this._pickId = this._pickId && this._pickId.destroy();
this._billboardCollection = undefined;
};
/**
* A function that creates an image.
* @callback Billboard~CreateImageCallback
* @param {String} id The identifier of the image to load.
* @returns {Image|Canvas|Promise} The image, or a promise that will resolve to an image.
*/
return Billboard;
});
/*global define*/
define('Renderer/VertexArrayFacade',[
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Math',
'./Buffer',
'./BufferUsage',
'./VertexArray'
], function(
ComponentDatatype,
defaultValue,
defined,
destroyObject,
DeveloperError,
CesiumMath,
Buffer,
BufferUsage,
VertexArray) {
'use strict';
/**
* @private
*/
function VertexArrayFacade(context, attributes, sizeInVertices, instanced) {
if (!context) {
throw new DeveloperError('context is required.');
}
if (!attributes || (attributes.length === 0)) {
throw new DeveloperError('At least one attribute is required.');
}
var attrs = VertexArrayFacade._verifyAttributes(attributes);
sizeInVertices = defaultValue(sizeInVertices, 0);
var precreatedAttributes = [];
var attributesByUsage = {};
var attributesForUsage;
var usage;
// Bucket the attributes by usage.
var length = attrs.length;
for (var i = 0; i < length; ++i) {
var attribute = attrs[i];
// If the attribute already has a vertex buffer, we do not need
// to manage a vertex buffer or typed array for it.
if (attribute.vertexBuffer) {
precreatedAttributes.push(attribute);
continue;
}
usage = attribute.usage;
attributesForUsage = attributesByUsage[usage];
if (!defined(attributesForUsage)) {
attributesForUsage = attributesByUsage[usage] = [];
}
attributesForUsage.push(attribute);
}
// A function to sort attributes by the size of their components. From left to right, a vertex
// stores floats, shorts, and then bytes.
function compare(left, right) {
return ComponentDatatype.getSizeInBytes(right.componentDatatype) - ComponentDatatype.getSizeInBytes(left.componentDatatype);
}
this._allBuffers = [];
for (usage in attributesByUsage) {
if (attributesByUsage.hasOwnProperty(usage)) {
attributesForUsage = attributesByUsage[usage];
attributesForUsage.sort(compare);
var vertexSizeInBytes = VertexArrayFacade._vertexSizeInBytes(attributesForUsage);
var bufferUsage = attributesForUsage[0].usage;
var buffer = {
vertexSizeInBytes : vertexSizeInBytes,
vertexBuffer : undefined,
usage : bufferUsage,
needsCommit : false,
arrayBuffer : undefined,
arrayViews : VertexArrayFacade._createArrayViews(attributesForUsage, vertexSizeInBytes)
};
this._allBuffers.push(buffer);
}
}
this._size = 0;
this._instanced = defaultValue(instanced, false);
this._precreated = precreatedAttributes;
this._context = context;
this.writers = undefined;
this.va = undefined;
this.resize(sizeInVertices);
}
VertexArrayFacade._verifyAttributes = function(attributes) {
var attrs = [];
for ( var i = 0; i < attributes.length; ++i) {
var attribute = attributes[i];
var attr = {
index : defaultValue(attribute.index, i),
enabled : defaultValue(attribute.enabled, true),
componentsPerAttribute : attribute.componentsPerAttribute,
componentDatatype : defaultValue(attribute.componentDatatype, ComponentDatatype.FLOAT),
normalize : defaultValue(attribute.normalize, false),
// There will be either a vertexBuffer or an [optional] usage.
vertexBuffer : attribute.vertexBuffer,
usage : defaultValue(attribute.usage, BufferUsage.STATIC_DRAW)
};
attrs.push(attr);
if ((attr.componentsPerAttribute !== 1) && (attr.componentsPerAttribute !== 2) && (attr.componentsPerAttribute !== 3) && (attr.componentsPerAttribute !== 4)) {
throw new DeveloperError('attribute.componentsPerAttribute must be in the range [1, 4].');
}
var datatype = attr.componentDatatype;
if (!ComponentDatatype.validate(datatype)) {
throw new DeveloperError('Attribute must have a valid componentDatatype or not specify it.');
}
if (!BufferUsage.validate(attr.usage)) {
throw new DeveloperError('Attribute must have a valid usage or not specify it.');
}
}
// Verify all attribute names are unique.
var uniqueIndices = new Array(attrs.length);
for ( var j = 0; j < attrs.length; ++j) {
var currentAttr = attrs[j];
var index = currentAttr.index;
if (uniqueIndices[index]) {
throw new DeveloperError('Index ' + index + ' is used by more than one attribute.');
}
uniqueIndices[index] = true;
}
return attrs;
};
VertexArrayFacade._vertexSizeInBytes = function(attributes) {
var sizeInBytes = 0;
var length = attributes.length;
for ( var i = 0; i < length; ++i) {
var attribute = attributes[i];
sizeInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(attribute.componentDatatype));
}
var maxComponentSizeInBytes = (length > 0) ? ComponentDatatype.getSizeInBytes(attributes[0].componentDatatype) : 0; // Sorted by size
var remainder = (maxComponentSizeInBytes > 0) ? (sizeInBytes % maxComponentSizeInBytes) : 0;
var padding = (remainder === 0) ? 0 : (maxComponentSizeInBytes - remainder);
sizeInBytes += padding;
return sizeInBytes;
};
VertexArrayFacade._createArrayViews = function(attributes, vertexSizeInBytes) {
var views = [];
var offsetInBytes = 0;
var length = attributes.length;
for ( var i = 0; i < length; ++i) {
var attribute = attributes[i];
var componentDatatype = attribute.componentDatatype;
views.push({
index : attribute.index,
enabled : attribute.enabled,
componentsPerAttribute : attribute.componentsPerAttribute,
componentDatatype : componentDatatype,
normalize : attribute.normalize,
offsetInBytes : offsetInBytes,
vertexSizeInComponentType : vertexSizeInBytes / ComponentDatatype.getSizeInBytes(componentDatatype),
view : undefined
});
offsetInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(componentDatatype));
}
return views;
};
/**
* Invalidates writers. Can't render again until commit is called.
*/
VertexArrayFacade.prototype.resize = function(sizeInVertices) {
this._size = sizeInVertices;
var allBuffers = this._allBuffers;
this.writers = [];
for (var i = 0, len = allBuffers.length; i < len; ++i) {
var buffer = allBuffers[i];
VertexArrayFacade._resize(buffer, this._size);
// Reserving invalidates the writers, so if client's cache them, they need to invalidate their cache.
VertexArrayFacade._appendWriters(this.writers, buffer);
}
// VAs are recreated next time commit is called.
destroyVA(this);
};
VertexArrayFacade._resize = function(buffer, size) {
if (buffer.vertexSizeInBytes > 0) {
// Create larger array buffer
var arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes);
// Copy contents from previous array buffer
if (defined(buffer.arrayBuffer)) {
var destView = new Uint8Array(arrayBuffer);
var sourceView = new Uint8Array(buffer.arrayBuffer);
var sourceLength = sourceView.length;
for ( var j = 0; j < sourceLength; ++j) {
destView[j] = sourceView[j];
}
}
// Create typed views into the new array buffer
var views = buffer.arrayViews;
var length = views.length;
for ( var i = 0; i < length; ++i) {
var view = views[i];
view.view = ComponentDatatype.createArrayBufferView(view.componentDatatype, arrayBuffer, view.offsetInBytes);
}
buffer.arrayBuffer = arrayBuffer;
}
};
var createWriters = [
// 1 component per attribute
function(buffer, view, vertexSizeInComponentType) {
return function(index, attribute) {
view[index * vertexSizeInComponentType] = attribute;
buffer.needsCommit = true;
};
},
// 2 component per attribute
function(buffer, view, vertexSizeInComponentType) {
return function(index, component0, component1) {
var i = index * vertexSizeInComponentType;
view[i] = component0;
view[i + 1] = component1;
buffer.needsCommit = true;
};
},
// 3 component per attribute
function(buffer, view, vertexSizeInComponentType) {
return function(index, component0, component1, component2) {
var i = index * vertexSizeInComponentType;
view[i] = component0;
view[i + 1] = component1;
view[i + 2] = component2;
buffer.needsCommit = true;
};
},
// 4 component per attribute
function(buffer, view, vertexSizeInComponentType) {
return function(index, component0, component1, component2, component3) {
var i = index * vertexSizeInComponentType;
view[i] = component0;
view[i + 1] = component1;
view[i + 2] = component2;
view[i + 3] = component3;
buffer.needsCommit = true;
};
}];
VertexArrayFacade._appendWriters = function(writers, buffer) {
var arrayViews = buffer.arrayViews;
var length = arrayViews.length;
for ( var i = 0; i < length; ++i) {
var arrayView = arrayViews[i];
writers[arrayView.index] = createWriters[arrayView.componentsPerAttribute - 1](buffer, arrayView.view, arrayView.vertexSizeInComponentType);
}
};
VertexArrayFacade.prototype.commit = function(indexBuffer) {
var recreateVA = false;
var allBuffers = this._allBuffers;
var buffer;
var i;
var length;
for (i = 0, length = allBuffers.length; i < length; ++i) {
buffer = allBuffers[i];
recreateVA = commit(this, buffer) || recreateVA;
}
///////////////////////////////////////////////////////////////////////
if (recreateVA || !defined(this.va)) {
destroyVA(this);
var va = this.va = [];
var numberOfVertexArrays = defined(indexBuffer) ? Math.ceil(this._size / (CesiumMath.SIXTY_FOUR_KILOBYTES - 1)) : 1;
for ( var k = 0; k < numberOfVertexArrays; ++k) {
var attributes = [];
for (i = 0, length = allBuffers.length; i < length; ++i) {
buffer = allBuffers[i];
var offset = k * (buffer.vertexSizeInBytes * (CesiumMath.SIXTY_FOUR_KILOBYTES - 1));
VertexArrayFacade._appendAttributes(attributes, buffer, offset, this._instanced);
}
attributes = attributes.concat(this._precreated);
va.push({
va : new VertexArray({
context : this._context,
attributes : attributes,
indexBuffer : indexBuffer
}),
indicesCount : 1.5 * ((k !== (numberOfVertexArrays - 1)) ? (CesiumMath.SIXTY_FOUR_KILOBYTES - 1) : (this._size % (CesiumMath.SIXTY_FOUR_KILOBYTES - 1)))
// TODO: not hardcode 1.5, this assumes 6 indices per 4 vertices (as for Billboard quads).
});
}
}
};
function commit(vertexArrayFacade, buffer) {
if (buffer.needsCommit && (buffer.vertexSizeInBytes > 0)) {
buffer.needsCommit = false;
var vertexBuffer = buffer.vertexBuffer;
var vertexBufferSizeInBytes = vertexArrayFacade._size * buffer.vertexSizeInBytes;
var vertexBufferDefined = defined(vertexBuffer);
if (!vertexBufferDefined || (vertexBuffer.sizeInBytes < vertexBufferSizeInBytes)) {
if (vertexBufferDefined) {
vertexBuffer.destroy();
}
buffer.vertexBuffer = Buffer.createVertexBuffer({
context : vertexArrayFacade._context,
typedArray : buffer.arrayBuffer,
usage : buffer.usage
});
buffer.vertexBuffer.vertexArrayDestroyable = false;
return true; // Created new vertex buffer
}
buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer);
}
return false; // Did not create new vertex buffer
}
VertexArrayFacade._appendAttributes = function(attributes, buffer, vertexBufferOffset, instanced) {
var arrayViews = buffer.arrayViews;
var length = arrayViews.length;
for ( var i = 0; i < length; ++i) {
var view = arrayViews[i];
attributes.push({
index : view.index,
enabled : view.enabled,
componentsPerAttribute : view.componentsPerAttribute,
componentDatatype : view.componentDatatype,
normalize : view.normalize,
vertexBuffer : buffer.vertexBuffer,
offsetInBytes : vertexBufferOffset + view.offsetInBytes,
strideInBytes : buffer.vertexSizeInBytes,
instanceDivisor : instanced ? 1 : 0
});
}
};
VertexArrayFacade.prototype.subCommit = function(offsetInVertices, lengthInVertices) {
if (offsetInVertices < 0 || offsetInVertices >= this._size) {
throw new DeveloperError('offsetInVertices must be greater than or equal to zero and less than the vertex array size.');
}
if (offsetInVertices + lengthInVertices > this._size) {
throw new DeveloperError('offsetInVertices + lengthInVertices cannot exceed the vertex array size.');
}
var allBuffers = this._allBuffers;
for (var i = 0, len = allBuffers.length; i < len; ++i) {
subCommit(allBuffers[i], offsetInVertices, lengthInVertices);
}
};
function subCommit(buffer, offsetInVertices, lengthInVertices) {
if (buffer.needsCommit && (buffer.vertexSizeInBytes > 0)) {
var byteOffset = buffer.vertexSizeInBytes * offsetInVertices;
var byteLength = buffer.vertexSizeInBytes * lengthInVertices;
// PERFORMANCE_IDEA: If we want to get really crazy, we could consider updating
// individual attributes instead of the entire (sub-)vertex.
//
// PERFORMANCE_IDEA: Does creating the typed view add too much GC overhead?
buffer.vertexBuffer.copyFromArrayView(new Uint8Array(buffer.arrayBuffer, byteOffset, byteLength), byteOffset);
}
}
VertexArrayFacade.prototype.endSubCommits = function() {
var allBuffers = this._allBuffers;
for (var i = 0, len = allBuffers.length; i < len; ++i) {
allBuffers[i].needsCommit = false;
}
};
function destroyVA(vertexArrayFacade) {
var va = vertexArrayFacade.va;
if (!defined(va)) {
return;
}
var length = va.length;
for (var i = 0; i < length; ++i) {
va[i].va.destroy();
}
vertexArrayFacade.va = undefined;
}
VertexArrayFacade.prototype.isDestroyed = function() {
return false;
};
VertexArrayFacade.prototype.destroy = function() {
var allBuffers = this._allBuffers;
for (var i = 0, len = allBuffers.length; i < len; ++i) {
var buffer = allBuffers[i];
buffer.vertexBuffer = buffer.vertexBuffer && buffer.vertexBuffer.destroy();
}
destroyVA(this);
return destroyObject(this);
};
return VertexArrayFacade;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/BillboardCollectionFS',[],function() {
'use strict';
return "uniform sampler2D u_atlas;\n\
varying vec2 v_textureCoordinates;\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#else\n\
varying vec4 v_color;\n\
#endif\n\
void main()\n\
{\n\
#ifdef RENDER_FOR_PICK\n\
vec4 vertexColor = vec4(1.0, 1.0, 1.0, 1.0);\n\
#else\n\
vec4 vertexColor = v_color;\n\
#endif\n\
vec4 color = texture2D(u_atlas, v_textureCoordinates) * vertexColor;\n\
if (color.a == 0.0)\n\
{\n\
discard;\n\
}\n\
#ifdef RENDER_FOR_PICK\n\
gl_FragColor = v_pickColor;\n\
#else\n\
gl_FragColor = color;\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/BillboardCollectionVS',[],function() {
'use strict';
return "#ifdef INSTANCED\n\
attribute vec2 direction;\n\
#endif\n\
attribute vec4 positionHighAndScale;\n\
attribute vec4 positionLowAndRotation;\n\
attribute vec4 compressedAttribute0;\n\
attribute vec4 compressedAttribute1;\n\
attribute vec4 compressedAttribute2;\n\
attribute vec4 eyeOffset;\n\
attribute vec4 scaleByDistance;\n\
attribute vec4 pixelOffsetScaleByDistance;\n\
attribute vec2 distanceDisplayCondition;\n\
varying vec2 v_textureCoordinates;\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#else\n\
varying vec4 v_color;\n\
#endif\n\
const float UPPER_BOUND = 32768.0;\n\
const float SHIFT_LEFT16 = 65536.0;\n\
const float SHIFT_LEFT8 = 256.0;\n\
const float SHIFT_LEFT7 = 128.0;\n\
const float SHIFT_LEFT5 = 32.0;\n\
const float SHIFT_LEFT3 = 8.0;\n\
const float SHIFT_LEFT2 = 4.0;\n\
const float SHIFT_LEFT1 = 2.0;\n\
const float SHIFT_RIGHT8 = 1.0 / 256.0;\n\
const float SHIFT_RIGHT7 = 1.0 / 128.0;\n\
const float SHIFT_RIGHT5 = 1.0 / 32.0;\n\
const float SHIFT_RIGHT3 = 1.0 / 8.0;\n\
const float SHIFT_RIGHT2 = 1.0 / 4.0;\n\
const float SHIFT_RIGHT1 = 1.0 / 2.0;\n\
vec4 computePositionWindowCoordinates(vec4 positionEC, vec2 imageSize, float scale, vec2 direction, vec2 origin, vec2 translate, vec2 pixelOffset, vec3 alignedAxis, bool validAlignedAxis, float rotation, bool sizeInMeters)\n\
{\n\
vec2 halfSize = imageSize * scale * czm_resolutionScale * 0.5;\n\
halfSize *= ((direction * 2.0) - 1.0);\n\
vec2 originTranslate = origin * abs(halfSize);\n\
#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\
if (validAlignedAxis || rotation != 0.0)\n\
{\n\
float angle = rotation;\n\
if (validAlignedAxis)\n\
{\n\
vec3 pos = positionEC.xyz + czm_encodedCameraPositionMCHigh + czm_encodedCameraPositionMCLow;\n\
vec3 normal = normalize(cross(alignedAxis, pos));\n\
vec4 tangent = vec4(normalize(cross(pos, normal)), 0.0);\n\
tangent = czm_modelViewProjection * tangent;\n\
angle += sign(-tangent.x) * acos(tangent.y / length(tangent.xy));\n\
}\n\
float cosTheta = cos(angle);\n\
float sinTheta = sin(angle);\n\
mat2 rotationMatrix = mat2(cosTheta, sinTheta, -sinTheta, cosTheta);\n\
halfSize = rotationMatrix * halfSize;\n\
}\n\
#endif\n\
if (sizeInMeters)\n\
{\n\
positionEC.xy += halfSize;\n\
}\n\
vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
if (sizeInMeters)\n\
{\n\
originTranslate += originTranslate / czm_metersPerPixel(positionEC);\n\
}\n\
positionWC.xy += originTranslate;\n\
if (!sizeInMeters)\n\
{\n\
positionWC.xy += halfSize;\n\
}\n\
positionWC.xy += translate;\n\
positionWC.xy += (pixelOffset * czm_resolutionScale);\n\
return positionWC;\n\
}\n\
void main()\n\
{\n\
vec3 positionHigh = positionHighAndScale.xyz;\n\
vec3 positionLow = positionLowAndRotation.xyz;\n\
float scale = positionHighAndScale.w;\n\
#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\
float rotation = positionLowAndRotation.w;\n\
#else\n\
float rotation = 0.0;\n\
#endif\n\
float compressed = compressedAttribute0.x;\n\
vec2 pixelOffset;\n\
pixelOffset.x = floor(compressed * SHIFT_RIGHT7);\n\
compressed -= pixelOffset.x * SHIFT_LEFT7;\n\
pixelOffset.x -= UPPER_BOUND;\n\
vec2 origin;\n\
origin.x = floor(compressed * SHIFT_RIGHT5);\n\
compressed -= origin.x * SHIFT_LEFT5;\n\
origin.y = floor(compressed * SHIFT_RIGHT3);\n\
compressed -= origin.y * SHIFT_LEFT3;\n\
origin -= vec2(1.0);\n\
float show = floor(compressed * SHIFT_RIGHT2);\n\
compressed -= show * SHIFT_LEFT2;\n\
#ifdef INSTANCED\n\
vec2 textureCoordinatesBottomLeft = czm_decompressTextureCoordinates(compressedAttribute0.w);\n\
vec2 textureCoordinatesRange = czm_decompressTextureCoordinates(eyeOffset.w);\n\
vec2 textureCoordinates = textureCoordinatesBottomLeft + direction * textureCoordinatesRange;\n\
#else\n\
vec2 direction;\n\
direction.x = floor(compressed * SHIFT_RIGHT1);\n\
direction.y = compressed - direction.x * SHIFT_LEFT1;\n\
vec2 textureCoordinates = czm_decompressTextureCoordinates(compressedAttribute0.w);\n\
#endif\n\
float temp = compressedAttribute0.y * SHIFT_RIGHT8;\n\
pixelOffset.y = -(floor(temp) - UPPER_BOUND);\n\
vec2 translate;\n\
translate.y = (temp - floor(temp)) * SHIFT_LEFT16;\n\
temp = compressedAttribute0.z * SHIFT_RIGHT8;\n\
translate.x = floor(temp) - UPPER_BOUND;\n\
translate.y += (temp - floor(temp)) * SHIFT_LEFT8;\n\
translate.y -= UPPER_BOUND;\n\
temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\
vec2 imageSize = vec2(floor(temp), compressedAttribute2.w);\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
vec4 translucencyByDistance;\n\
translucencyByDistance.x = compressedAttribute1.z;\n\
translucencyByDistance.z = compressedAttribute1.w;\n\
translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\
translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
#endif\n\
#ifdef ALIGNED_AXIS\n\
vec3 alignedAxis = czm_octDecode(floor(compressedAttribute1.y * SHIFT_RIGHT8));\n\
temp = compressedAttribute2.z * SHIFT_RIGHT5;\n\
bool validAlignedAxis = (temp - floor(temp)) * SHIFT_LEFT1 > 0.0;\n\
#else\n\
vec3 alignedAxis = vec3(0.0);\n\
bool validAlignedAxis = false;\n\
#endif\n\
#ifdef RENDER_FOR_PICK\n\
temp = compressedAttribute2.y;\n\
#else\n\
temp = compressedAttribute2.x;\n\
#endif\n\
vec4 color;\n\
temp = temp * SHIFT_RIGHT8;\n\
color.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
temp = floor(temp) * SHIFT_RIGHT8;\n\
color.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
color.r = floor(temp);\n\
temp = compressedAttribute2.z * SHIFT_RIGHT8;\n\
bool sizeInMeters = floor((temp - floor(temp)) * SHIFT_LEFT7) > 0.0;\n\
temp = floor(temp) * SHIFT_RIGHT8;\n\
#ifdef RENDER_FOR_PICK\n\
color.a = (temp - floor(temp)) * SHIFT_LEFT8;\n\
vec4 pickColor = color / 255.0;\n\
#else\n\
color.a = floor(temp);\n\
color /= 255.0;\n\
#endif\n\
vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\
vec4 positionEC = czm_modelViewRelativeToEye * p;\n\
positionEC = czm_eyeOffset(positionEC, eyeOffset.xyz);\n\
positionEC.xyz *= show;\n\
#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(EYE_DISTANCE_PIXEL_OFFSET) || defined(DISTANCE_DISPLAY_CONDITION)\n\
float lengthSq;\n\
if (czm_sceneMode == czm_sceneMode2D)\n\
{\n\
lengthSq = czm_eyeHeight2D.y;\n\
}\n\
else\n\
{\n\
lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\
}\n\
#endif\n\
#ifdef EYE_DISTANCE_SCALING\n\
scale *= czm_nearFarScalar(scaleByDistance, lengthSq);\n\
if (scale == 0.0)\n\
{\n\
positionEC.xyz = vec3(0.0);\n\
}\n\
#endif\n\
float translucency = 1.0;\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\
if (translucency == 0.0)\n\
{\n\
positionEC.xyz = vec3(0.0);\n\
}\n\
#endif\n\
#ifdef EYE_DISTANCE_PIXEL_OFFSET\n\
float pixelOffsetScale = czm_nearFarScalar(pixelOffsetScaleByDistance, lengthSq);\n\
pixelOffset *= pixelOffsetScale;\n\
#endif\n\
#ifdef DISTANCE_DISPLAY_CONDITION\n\
float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x;\n\
float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y;\n\
if (lengthSq < nearSq || lengthSq > farSq)\n\
{\n\
positionEC.xyz = vec3(0.0);\n\
}\n\
#endif\n\
vec4 positionWC = computePositionWindowCoordinates(positionEC, imageSize, scale, direction, origin, translate, pixelOffset, alignedAxis, validAlignedAxis, rotation, sizeInMeters);\n\
gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0);\n\
v_textureCoordinates = textureCoordinates;\n\
#ifdef RENDER_FOR_PICK\n\
v_pickColor = pickColor;\n\
#else\n\
v_color = color;\n\
v_color.a *= translucency;\n\
#endif\n\
}\n\
";
});
/*global define*/
define('Renderer/Framebuffer',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/PixelFormat',
'./ContextLimits'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
PixelFormat,
ContextLimits) {
'use strict';
function attachTexture(framebuffer, attachment, texture) {
var gl = framebuffer._gl;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, texture._target, texture._texture, 0);
}
function attachRenderbuffer(framebuffer, attachment, renderbuffer) {
var gl = framebuffer._gl;
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer._getRenderbuffer());
}
/**
* Creates a framebuffer with optional initial color, depth, and stencil attachments.
* Framebuffers are used for render-to-texture effects; they allow us to render to
* textures in one pass, and read from it in a later pass.
*
* @param {Object} options The initial framebuffer attachments as shown in the example below. context
is required. The possible properties are colorTextures
, colorRenderbuffers
, depthTexture
, depthRenderbuffer
, stencilRenderbuffer
, depthStencilTexture
, and depthStencilRenderbuffer
.
*
* @exception {DeveloperError} Cannot have both color texture and color renderbuffer attachments.
* @exception {DeveloperError} Cannot have both a depth texture and depth renderbuffer attachment.
* @exception {DeveloperError} Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.
* @exception {DeveloperError} Cannot have both a depth and depth-stencil renderbuffer.
* @exception {DeveloperError} Cannot have both a stencil and depth-stencil renderbuffer.
* @exception {DeveloperError} Cannot have both a depth and stencil renderbuffer.
* @exception {DeveloperError} The color-texture pixel-format must be a color format.
* @exception {DeveloperError} The depth-texture pixel-format must be DEPTH_COMPONENT.
* @exception {DeveloperError} The depth-stencil-texture pixel-format must be DEPTH_STENCIL.
* @exception {DeveloperError} The number of color attachments exceeds the number supported.
*
* @example
* // Create a framebuffer with color and depth texture attachments.
* var width = context.canvas.clientWidth;
* var height = context.canvas.clientHeight;
* var framebuffer = new Framebuffer({
* context : context,
* colorTextures : [new Texture({
* context : context,
* width : width,
* height : height,
* pixelFormat : PixelFormat.RGBA
* })],
* depthTexture : new Texture({
* context : context,
* width : width,
* height : height,
* pixelFormat : PixelFormat.DEPTH_COMPONENT,
* pixelDatatype : PixelDatatype.UNSIGNED_SHORT
* })
* });
*
* @private
*/
function Framebuffer(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
var gl = options.context._gl;
var maximumColorAttachments = ContextLimits.maximumColorAttachments;
this._gl = gl;
this._framebuffer = gl.createFramebuffer();
this._colorTextures = [];
this._colorRenderbuffers = [];
this._activeColorAttachments = [];
this._depthTexture = undefined;
this._depthRenderbuffer = undefined;
this._stencilRenderbuffer = undefined;
this._depthStencilTexture = undefined;
this._depthStencilRenderbuffer = undefined;
/**
* When true, the framebuffer owns its attachments so they will be destroyed when
* {@link Framebuffer#destroy} is called or when a new attachment is assigned
* to an attachment point.
*
* @type {Boolean}
* @default true
*
* @see Framebuffer#destroy
*/
this.destroyAttachments = defaultValue(options.destroyAttachments, true);
// Throw if a texture and renderbuffer are attached to the same point. This won't
// cause a WebGL error (because only one will be attached), but is likely a developer error.
if (defined(options.colorTextures) && defined(options.colorRenderbuffers)) {
throw new DeveloperError('Cannot have both color texture and color renderbuffer attachments.');
}
if (defined(options.depthTexture) && defined(options.depthRenderbuffer)) {
throw new DeveloperError('Cannot have both a depth texture and depth renderbuffer attachment.');
}
if (defined(options.depthStencilTexture) && defined(options.depthStencilRenderbuffer)) {
throw new DeveloperError('Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.');
}
// Avoid errors defined in Section 6.5 of the WebGL spec
var depthAttachment = (defined(options.depthTexture) || defined(options.depthRenderbuffer));
var depthStencilAttachment = (defined(options.depthStencilTexture) || defined(options.depthStencilRenderbuffer));
if (depthAttachment && depthStencilAttachment) {
throw new DeveloperError('Cannot have both a depth and depth-stencil attachment.');
}
if (defined(options.stencilRenderbuffer) && depthStencilAttachment) {
throw new DeveloperError('Cannot have both a stencil and depth-stencil attachment.');
}
if (depthAttachment && defined(options.stencilRenderbuffer)) {
throw new DeveloperError('Cannot have both a depth and stencil attachment.');
}
///////////////////////////////////////////////////////////////////
this._bind();
var texture;
var renderbuffer;
var i;
var length;
var attachmentEnum;
if (defined(options.colorTextures)) {
var textures = options.colorTextures;
length = this._colorTextures.length = this._activeColorAttachments.length = textures.length;
if (length > maximumColorAttachments) {
throw new DeveloperError('The number of color attachments exceeds the number supported.');
}
for (i = 0; i < length; ++i) {
texture = textures[i];
if (!PixelFormat.isColorFormat(texture.pixelFormat)) {
throw new DeveloperError('The color-texture pixel-format must be a color format.');
}
attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
attachTexture(this, attachmentEnum, texture);
this._activeColorAttachments[i] = attachmentEnum;
this._colorTextures[i] = texture;
}
}
if (defined(options.colorRenderbuffers)) {
var renderbuffers = options.colorRenderbuffers;
length = this._colorRenderbuffers.length = this._activeColorAttachments.length = renderbuffers.length;
if (length > maximumColorAttachments) {
throw new DeveloperError('The number of color attachments exceeds the number supported.');
}
for (i = 0; i < length; ++i) {
renderbuffer = renderbuffers[i];
attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
attachRenderbuffer(this, attachmentEnum, renderbuffer);
this._activeColorAttachments[i] = attachmentEnum;
this._colorRenderbuffers[i] = renderbuffer;
}
}
if (defined(options.depthTexture)) {
texture = options.depthTexture;
if (texture.pixelFormat !== PixelFormat.DEPTH_COMPONENT) {
throw new DeveloperError('The depth-texture pixel-format must be DEPTH_COMPONENT.');
}
attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture);
this._depthTexture = texture;
}
if (defined(options.depthRenderbuffer)) {
renderbuffer = options.depthRenderbuffer;
attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer);
this._depthRenderbuffer = renderbuffer;
}
if (defined(options.stencilRenderbuffer)) {
renderbuffer = options.stencilRenderbuffer;
attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer);
this._stencilRenderbuffer = renderbuffer;
}
if (defined(options.depthStencilTexture)) {
texture = options.depthStencilTexture;
if (texture.pixelFormat !== PixelFormat.DEPTH_STENCIL) {
throw new DeveloperError('The depth-stencil pixel-format must be DEPTH_STENCIL.');
}
attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture);
this._depthStencilTexture = texture;
}
if (defined(options.depthStencilRenderbuffer)) {
renderbuffer = options.depthStencilRenderbuffer;
attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer);
this._depthStencilRenderbuffer = renderbuffer;
}
this._unBind();
}
defineProperties(Framebuffer.prototype, {
/**
* The status of the framebuffer. If the status is not WebGLConstants.FRAMEBUFFER_COMPLETE,
* a {@link DeveloperError} will be thrown when attempting to render to the framebuffer.
* @memberof Framebuffer.prototype
* @type {Number}
*/
status : {
get : function() {
this._bind();
var status = this._gl.checkFramebufferStatus(this._gl.FRAMEBUFFER);
this._unBind();
return status;
}
},
numberOfColorAttachments : {
get : function() {
return this._activeColorAttachments.length;
}
},
depthTexture: {
get : function() {
return this._depthTexture;
}
},
depthRenderbuffer: {
get : function() {
return this._depthRenderbuffer;
}
},
stencilRenderbuffer : {
get : function() {
return this._stencilRenderbuffer;
}
},
depthStencilTexture : {
get : function() {
return this._depthStencilTexture;
}
},
depthStencilRenderbuffer : {
get : function() {
return this._depthStencilRenderbuffer;
}
},
/**
* True if the framebuffer has a depth attachment. Depth attachments include
* depth and depth-stencil textures, and depth and depth-stencil renderbuffers. When
* rendering to a framebuffer, a depth attachment is required for the depth test to have effect.
* @memberof Framebuffer.prototype
* @type {Boolean}
*/
hasDepthAttachment : {
get : function() {
return !!(this.depthTexture || this.depthRenderbuffer || this.depthStencilTexture || this.depthStencilRenderbuffer);
}
}
});
Framebuffer.prototype._bind = function() {
var gl = this._gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer);
};
Framebuffer.prototype._unBind = function() {
var gl = this._gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
};
Framebuffer.prototype._getActiveColorAttachments = function() {
return this._activeColorAttachments;
};
Framebuffer.prototype.getColorTexture = function(index) {
if (!defined(index) || index < 0 || index >= this._colorTextures.length) {
throw new DeveloperError('index is required, must be greater than or equal to zero and must be less than the number of color attachments.');
}
return this._colorTextures[index];
};
Framebuffer.prototype.getColorRenderbuffer = function(index) {
if (!defined(index) || index < 0 || index >= this._colorRenderbuffers.length) {
throw new DeveloperError('index is required, must be greater than or equal to zero and must be less than the number of color attachments.');
}
return this._colorRenderbuffers[index];
};
Framebuffer.prototype.isDestroyed = function() {
return false;
};
Framebuffer.prototype.destroy = function() {
if (this.destroyAttachments) {
// If the color texture is a cube map face, it is owned by the cube map, and will not be destroyed.
var i = 0;
var textures = this._colorTextures;
var length = textures.length;
for (; i < length; ++i) {
var texture = textures[i];
if (defined(texture)) {
texture.destroy();
}
}
var renderbuffers = this._colorRenderbuffers;
length = renderbuffers.length;
for (i = 0; i < length; ++i) {
var renderbuffer = renderbuffers[i];
if (defined(renderbuffer)) {
renderbuffer.destroy();
}
}
this._depthTexture = this._depthTexture && this._depthTexture.destroy();
this._depthRenderbuffer = this._depthRenderbuffer && this._depthRenderbuffer.destroy();
this._stencilRenderbuffer = this._stencilRenderbuffer && this._stencilRenderbuffer.destroy();
this._depthStencilTexture = this._depthStencilTexture && this._depthStencilTexture.destroy();
this._depthStencilRenderbuffer = this._depthStencilRenderbuffer && this._depthStencilRenderbuffer.destroy();
}
this._gl.deleteFramebuffer(this._framebuffer);
return destroyObject(this);
};
return Framebuffer;
});
/*global define*/
define('Scene/TextureAtlas',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/loadImage',
'../Core/PixelFormat',
'../Core/RuntimeError',
'../Renderer/Framebuffer',
'../Renderer/Texture',
'../ThirdParty/when'
], function(
BoundingRectangle,
Cartesian2,
createGuid,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
loadImage,
PixelFormat,
RuntimeError,
Framebuffer,
Texture,
when) {
'use strict';
// The atlas is made up of regions of space called nodes that contain images or child nodes.
function TextureAtlasNode(bottomLeft, topRight, childNode1, childNode2, imageIndex) {
this.bottomLeft = defaultValue(bottomLeft, Cartesian2.ZERO);
this.topRight = defaultValue(topRight, Cartesian2.ZERO);
this.childNode1 = childNode1;
this.childNode2 = childNode2;
this.imageIndex = imageIndex;
}
var defaultInitialSize = new Cartesian2(16.0, 16.0);
/**
* A TextureAtlas stores multiple images in one square texture and keeps
* track of the texture coordinates for each image. TextureAtlas is dynamic,
* meaning new images can be added at any point in time.
* Texture coordinates are subject to change if the texture atlas resizes, so it is
* important to check {@link TextureAtlas#getGUID} before using old values.
*
* @alias TextureAtlas
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Scene} options.context The context in which the texture gets created.
* @param {PixelFormat} [options.pixelFormat=PixelFormat.RGBA] The pixel format of the texture.
* @param {Number} [options.borderWidthInPixels=1] The amount of spacing between adjacent images in pixels.
* @param {Cartesian2} [options.initialSize=new Cartesian2(16.0, 16.0)] The initial side lengths of the texture.
*
* @exception {DeveloperError} borderWidthInPixels must be greater than or equal to zero.
* @exception {DeveloperError} initialSize must be greater than zero.
*
* @private
*/
function TextureAtlas(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var borderWidthInPixels = defaultValue(options.borderWidthInPixels, 1.0);
var initialSize = defaultValue(options.initialSize, defaultInitialSize);
if (!defined(options.context)) {
throw new DeveloperError('context is required.');
}
if (borderWidthInPixels < 0) {
throw new DeveloperError('borderWidthInPixels must be greater than or equal to zero.');
}
if (initialSize.x < 1 || initialSize.y < 1) {
throw new DeveloperError('initialSize must be greater than zero.');
}
this._context = options.context;
this._pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
this._borderWidthInPixels = borderWidthInPixels;
this._textureCoordinates = [];
this._guid = createGuid();
this._idHash = {};
this._initialSize = initialSize;
this._root = undefined;
}
defineProperties(TextureAtlas.prototype, {
/**
* The amount of spacing between adjacent images in pixels.
* @memberof TextureAtlas.prototype
* @type {Number}
*/
borderWidthInPixels : {
get : function() {
return this._borderWidthInPixels;
}
},
/**
* An array of {@link BoundingRectangle} texture coordinate regions for all the images in the texture atlas.
* The x and y values of the rectangle correspond to the bottom-left corner of the texture coordinate.
* The coordinates are in the order that the corresponding images were added to the atlas.
* @memberof TextureAtlas.prototype
* @type {BoundingRectangle[]}
*/
textureCoordinates : {
get : function() {
return this._textureCoordinates;
}
},
/**
* The texture that all of the images are being written to.
* @memberof TextureAtlas.prototype
* @type {Texture}
*/
texture : {
get : function() {
if(!defined(this._texture)) {
this._texture = new Texture({
context : this._context,
width : this._initialSize.x,
height : this._initialSize.y,
pixelFormat : this._pixelFormat
});
}
return this._texture;
}
},
/**
* The number of images in the texture atlas. This value increases
* every time addImage or addImages is called.
* Texture coordinates are subject to change if the texture atlas resizes, so it is
* important to check {@link TextureAtlas#getGUID} before using old values.
* @memberof TextureAtlas.prototype
* @type {Number}
*/
numberOfImages : {
get : function() {
return this._textureCoordinates.length;
}
},
/**
* The atlas' globally unique identifier (GUID).
* The GUID changes whenever the texture atlas is modified.
* Classes that use a texture atlas should check if the GUID
* has changed before processing the atlas data.
* @memberof TextureAtlas.prototype
* @type {String}
*/
guid : {
get : function() {
return this._guid;
}
}
});
// Builds a larger texture and copies the old texture into the new one.
function resizeAtlas(textureAtlas, image) {
var context = textureAtlas._context;
var numImages = textureAtlas.numberOfImages;
var scalingFactor = 2.0;
var borderWidthInPixels = textureAtlas._borderWidthInPixels;
if (numImages > 0) {
var oldAtlasWidth = textureAtlas._texture.width;
var oldAtlasHeight = textureAtlas._texture.height;
var atlasWidth = scalingFactor * (oldAtlasWidth + image.width + borderWidthInPixels);
var atlasHeight = scalingFactor * (oldAtlasHeight + image.height + borderWidthInPixels);
var widthRatio = oldAtlasWidth / atlasWidth;
var heightRatio = oldAtlasHeight / atlasHeight;
// Create new node structure, putting the old root node in the bottom left.
var nodeBottomRight = new TextureAtlasNode(new Cartesian2(oldAtlasWidth + borderWidthInPixels, borderWidthInPixels), new Cartesian2(atlasWidth, oldAtlasHeight));
var nodeBottomHalf = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, oldAtlasHeight), textureAtlas._root, nodeBottomRight);
var nodeTopHalf = new TextureAtlasNode(new Cartesian2(borderWidthInPixels, oldAtlasHeight + borderWidthInPixels), new Cartesian2(atlasWidth, atlasHeight));
var nodeMain = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, atlasHeight), nodeBottomHalf, nodeTopHalf);
// Resize texture coordinates.
for (var i = 0; i < textureAtlas._textureCoordinates.length; i++) {
var texCoord = textureAtlas._textureCoordinates[i];
if (defined(texCoord)) {
texCoord.x *= widthRatio;
texCoord.y *= heightRatio;
texCoord.width *= widthRatio;
texCoord.height *= heightRatio;
}
}
// Copy larger texture.
var newTexture = new Texture({
context : textureAtlas._context,
width : atlasWidth,
height : atlasHeight,
pixelFormat : textureAtlas._pixelFormat
});
var framebuffer = new Framebuffer({
context : context,
colorTextures : [textureAtlas._texture],
destroyAttachments : false
});
framebuffer._bind();
newTexture.copyFromFramebuffer(0, 0, 0, 0, atlasWidth, atlasHeight);
framebuffer._unBind();
framebuffer.destroy();
textureAtlas._texture = textureAtlas._texture && textureAtlas._texture.destroy();
textureAtlas._texture = newTexture;
textureAtlas._root = nodeMain;
} else {
// First image exceeds initialSize
var initialWidth = scalingFactor * (image.width + 2 * borderWidthInPixels);
var initialHeight = scalingFactor * (image.height + 2 * borderWidthInPixels);
if(initialWidth < textureAtlas._initialSize.x) {
initialWidth = textureAtlas._initialSize.x;
}
if(initialHeight < textureAtlas._initialSize.y) {
initialHeight = textureAtlas._initialSize.y;
}
textureAtlas._texture = textureAtlas._texture && textureAtlas._texture.destroy();
textureAtlas._texture = new Texture({
context : textureAtlas._context,
width : initialWidth,
height : initialHeight,
pixelFormat : textureAtlas._pixelFormat
});
textureAtlas._root = new TextureAtlasNode(new Cartesian2(borderWidthInPixels, borderWidthInPixels),
new Cartesian2(initialWidth, initialHeight));
}
}
// A recursive function that finds the best place to insert
// a new image based on existing image 'nodes'.
// Inspired by: http://blackpawn.com/texts/lightmaps/default.html
function findNode(textureAtlas, node, image) {
if (!defined(node)) {
return undefined;
}
// If a leaf node
if (!defined(node.childNode1) &&
!defined(node.childNode2)) {
// Node already contains an image, don't add to it.
if (defined(node.imageIndex)) {
return undefined;
}
var nodeWidth = node.topRight.x - node.bottomLeft.x;
var nodeHeight = node.topRight.y - node.bottomLeft.y;
var widthDifference = nodeWidth - image.width;
var heightDifference = nodeHeight - image.height;
// Node is smaller than the image.
if (widthDifference < 0 || heightDifference < 0) {
return undefined;
}
// If the node is the same size as the image, return the node
if (widthDifference === 0 && heightDifference === 0) {
return node;
}
// Vertical split (childNode1 = left half, childNode2 = right half).
if (widthDifference > heightDifference) {
node.childNode1 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, node.bottomLeft.y), new Cartesian2(node.bottomLeft.x + image.width, node.topRight.y));
// Only make a second child if the border gives enough space.
var childNode2BottomLeftX = node.bottomLeft.x + image.width + textureAtlas._borderWidthInPixels;
if (childNode2BottomLeftX < node.topRight.x) {
node.childNode2 = new TextureAtlasNode(new Cartesian2(childNode2BottomLeftX, node.bottomLeft.y), new Cartesian2(node.topRight.x, node.topRight.y));
}
}
// Horizontal split (childNode1 = bottom half, childNode2 = top half).
else {
node.childNode1 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, node.bottomLeft.y), new Cartesian2(node.topRight.x, node.bottomLeft.y + image.height));
// Only make a second child if the border gives enough space.
var childNode2BottomLeftY = node.bottomLeft.y + image.height + textureAtlas._borderWidthInPixels;
if (childNode2BottomLeftY < node.topRight.y) {
node.childNode2 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, childNode2BottomLeftY), new Cartesian2(node.topRight.x, node.topRight.y));
}
}
return findNode(textureAtlas, node.childNode1, image);
}
// If not a leaf node
return findNode(textureAtlas, node.childNode1, image) ||
findNode(textureAtlas, node.childNode2, image);
}
// Adds image of given index to the texture atlas. Called from addImage and addImages.
function addImage(textureAtlas, image, index) {
var node = findNode(textureAtlas, textureAtlas._root, image);
if (defined(node)) {
// Found a node that can hold the image.
node.imageIndex = index;
// Add texture coordinate and write to texture
var atlasWidth = textureAtlas._texture.width;
var atlasHeight = textureAtlas._texture.height;
var nodeWidth = node.topRight.x - node.bottomLeft.x;
var nodeHeight = node.topRight.y - node.bottomLeft.y;
var x = node.bottomLeft.x / atlasWidth;
var y = node.bottomLeft.y / atlasHeight;
var w = nodeWidth / atlasWidth;
var h = nodeHeight / atlasHeight;
textureAtlas._textureCoordinates[index] = new BoundingRectangle(x, y, w, h);
textureAtlas._texture.copyFrom(image, node.bottomLeft.x, node.bottomLeft.y);
} else {
// No node found, must resize the texture atlas.
resizeAtlas(textureAtlas, image);
addImage(textureAtlas, image, index);
}
textureAtlas._guid = createGuid();
}
/**
* Adds an image to the atlas. If the image is already in the atlas, the atlas is unchanged and
* the existing index is used.
*
* @param {String} id An identifier to detect whether the image already exists in the atlas.
* @param {Image|Canvas|String|Promise|TextureAtlas~CreateImageCallback} image An image or canvas to add to the texture atlas,
* or a URL to an Image, or a Promise for an image, or a function that creates an image.
* @returns {Promise.} A Promise for the image index.
*/
TextureAtlas.prototype.addImage = function(id, image) {
if (!defined(id)) {
throw new DeveloperError('id is required.');
}
if (!defined(image)) {
throw new DeveloperError('image is required.');
}
var indexPromise = this._idHash[id];
if (defined(indexPromise)) {
// we're already aware of this source
return indexPromise;
}
// not in atlas, create the promise for the index
if (typeof image === 'function') {
// if image is a function, call it
image = image(id);
if (!defined(image)) {
throw new DeveloperError('image is required.');
}
} else if (typeof image === 'string') {
// if image is a string, load it as an image
image = loadImage(image);
}
var that = this;
indexPromise = when(image, function(image) {
if (that.isDestroyed()) {
return -1;
}
var index = that.numberOfImages;
addImage(that, image, index);
return index;
});
// store the promise
this._idHash[id] = indexPromise;
return indexPromise;
};
/**
* Add a sub-region of an existing atlas image as additional image indices.
*
* @param {String} id The identifier of the existing image.
* @param {BoundingRectangle} subRegion An {@link BoundingRectangle} sub-region measured in pixels from the bottom-left.
*
* @returns {Promise.} A Promise for the image index.
*/
TextureAtlas.prototype.addSubRegion = function(id, subRegion) {
if (!defined(id)) {
throw new DeveloperError('id is required.');
}
if (!defined(subRegion)) {
throw new DeveloperError('subRegion is required.');
}
var indexPromise = this._idHash[id];
if (!defined(indexPromise)) {
throw new RuntimeError('image with id "' + id + '" not found in the atlas.');
}
var that = this;
return when(indexPromise, function(index) {
if (index === -1) {
// the atlas is destroyed
return -1;
}
var atlasWidth = that._texture.width;
var atlasHeight = that._texture.height;
var numImages = that.numberOfImages;
var baseRegion = that._textureCoordinates[index];
var x = baseRegion.x + (subRegion.x / atlasWidth);
var y = baseRegion.y + (subRegion.y / atlasHeight);
var w = subRegion.width / atlasWidth;
var h = subRegion.height / atlasHeight;
that._textureCoordinates.push(new BoundingRectangle(x, y, w, h));
that._guid = createGuid();
return numImages;
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see TextureAtlas#destroy
*/
TextureAtlas.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* atlas = atlas && atlas.destroy();
*
* @see TextureAtlas#isDestroyed
*/
TextureAtlas.prototype.destroy = function() {
this._texture = this._texture && this._texture.destroy();
return destroyObject(this);
};
/**
* A function that creates an image.
* @callback TextureAtlas~CreateImageCallback
* @param {String} id The identifier of the image to load.
* @returns {Image|Promise} The image, or a promise that will resolve to an image.
*/
return TextureAtlas;
});
/*global define*/
define('Scene/BillboardCollection',[
'../Core/AttributeCompression',
'../Core/BoundingSphere',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Color',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EncodedCartesian3',
'../Core/IndexDatatype',
'../Core/Math',
'../Core/Matrix4',
'../Core/WebGLConstants',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArrayFacade',
'../Shaders/BillboardCollectionFS',
'../Shaders/BillboardCollectionVS',
'./Billboard',
'./BlendingState',
'./HeightReference',
'./HorizontalOrigin',
'./SceneMode',
'./TextureAtlas',
'./VerticalOrigin'
], function(
AttributeCompression,
BoundingSphere,
Cartesian2,
Cartesian3,
Color,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EncodedCartesian3,
IndexDatatype,
CesiumMath,
Matrix4,
WebGLConstants,
Buffer,
BufferUsage,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
ShaderSource,
VertexArrayFacade,
BillboardCollectionFS,
BillboardCollectionVS,
Billboard,
BlendingState,
HeightReference,
HorizontalOrigin,
SceneMode,
TextureAtlas,
VerticalOrigin) {
'use strict';
var SHOW_INDEX = Billboard.SHOW_INDEX;
var POSITION_INDEX = Billboard.POSITION_INDEX;
var PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX;
var EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX;
var HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX;
var VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX;
var SCALE_INDEX = Billboard.SCALE_INDEX;
var IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX;
var COLOR_INDEX = Billboard.COLOR_INDEX;
var ROTATION_INDEX = Billboard.ROTATION_INDEX;
var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX;
var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX;
var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX;
var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX;
var DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION_INDEX;
var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;
var attributeLocations;
var attributeLocationsBatched = {
positionHighAndScale : 0,
positionLowAndRotation : 1,
compressedAttribute0 : 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates
compressedAttribute1 : 3, // aligned axis, translucency by distance, image width
compressedAttribute2 : 4, // image height, color, pick color, size in meters, valid aligned axis, 13 bits free
eyeOffset : 5, // 4 bytes free
scaleByDistance : 6,
pixelOffsetScaleByDistance : 7,
distanceDisplayCondition : 8
};
var attributeLocationsInstanced = {
direction : 0,
positionHighAndScale : 1,
positionLowAndRotation : 2, // texture offset in w
compressedAttribute0 : 3,
compressedAttribute1 : 4,
compressedAttribute2 : 5,
eyeOffset : 6, // texture range in w
scaleByDistance : 7,
pixelOffsetScaleByDistance : 8,
distanceDisplayCondition : 9
};
/**
* A renderable collection of billboards. Billboards are viewport-aligned
* images positioned in the 3D scene.
*
*
*
* Example billboards
*
*
* Billboards are added and removed from the collection using {@link BillboardCollection#add}
* and {@link BillboardCollection#remove}. Billboards in a collection automatically share textures
* for images with the same identifier.
*
* @alias BillboardCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each billboard from model to world coordinates.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
* @param {Scene} [options.scene] Must be passed in for billboards that use the height reference property or will be depth tested against the globe.
*
* @performance For best performance, prefer a few collections, each with many billboards, to
* many collections with only a few billboards each. Organize collections so that billboards
* with the same update frequency are in the same collection, i.e., billboards that do not
* change should be in one collection; billboards that change every frame should be in another
* collection; and so on.
*
* @see BillboardCollection#add
* @see BillboardCollection#remove
* @see Billboard
* @see LabelCollection
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
*
* @example
* // Create a billboard collection with two billboards
* var billboards = scene.primitives.add(new Cesium.BillboardCollection());
* billboards.add({
* position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
* image : 'url/to/image'
* });
* billboards.add({
* position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
* image : 'url/to/another/image'
* });
*/
function BillboardCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._scene = options.scene;
this._textureAtlas = undefined;
this._textureAtlasGUID = undefined;
this._destroyTextureAtlas = true;
this._sp = undefined;
this._rs = undefined;
this._vaf = undefined;
this._spPick = undefined;
this._billboards = [];
this._billboardsToUpdate = [];
this._billboardsToUpdateIndex = 0;
this._billboardsRemoved = false;
this._createVertexArray = false;
this._shaderRotation = false;
this._compiledShaderRotation = false;
this._compiledShaderRotationPick = false;
this._shaderAlignedAxis = false;
this._compiledShaderAlignedAxis = false;
this._compiledShaderAlignedAxisPick = false;
this._shaderScaleByDistance = false;
this._compiledShaderScaleByDistance = false;
this._compiledShaderScaleByDistancePick = false;
this._shaderTranslucencyByDistance = false;
this._compiledShaderTranslucencyByDistance = false;
this._compiledShaderTranslucencyByDistancePick = false;
this._shaderPixelOffsetScaleByDistance = false;
this._compiledShaderPixelOffsetScaleByDistance = false;
this._compiledShaderPixelOffsetScaleByDistancePick = false;
this._shaderDistanceDisplayCondition = false;
this._compiledShaderDistanceDisplayCondition = false;
this._compiledShaderDistanceDisplayConditionPick = false;
this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
this._maxSize = 0.0;
this._maxEyeOffset = 0.0;
this._maxScale = 1.0;
this._maxPixelOffset = 0.0;
this._allHorizontalCenter = true;
this._allVerticalCenter = true;
this._allSizedInMeters = true;
this._baseVolume = new BoundingSphere();
this._baseVolumeWC = new BoundingSphere();
this._baseVolume2D = new BoundingSphere();
this._boundingVolume = new BoundingSphere();
this._boundingVolumeDirty = false;
this._colorCommands = [];
this._pickCommands = [];
/**
* The 4x4 transformation matrix that transforms each billboard in this collection from model to world coordinates.
* When this is the identity matrix, the billboards are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*
*
* @example
* var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
* billboards.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
* });
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
* });
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
* });
* billboards.add({
* image : 'url/to/image',
* position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
* });
*
* @see Transforms.eastNorthUpToFixedFrame
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
this._mode = SceneMode.SCENE3D;
// The buffer usage for each attribute is determined based on the usage of the attribute over time.
this._buffersUsage = [
BufferUsage.STATIC_DRAW, // SHOW_INDEX
BufferUsage.STATIC_DRAW, // POSITION_INDEX
BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_INDEX
BufferUsage.STATIC_DRAW, // EYE_OFFSET_INDEX
BufferUsage.STATIC_DRAW, // HORIZONTAL_ORIGIN_INDEX
BufferUsage.STATIC_DRAW, // VERTICAL_ORIGIN_INDEX
BufferUsage.STATIC_DRAW, // SCALE_INDEX
BufferUsage.STATIC_DRAW, // IMAGE_INDEX_INDEX
BufferUsage.STATIC_DRAW, // COLOR_INDEX
BufferUsage.STATIC_DRAW, // ROTATION_INDEX
BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX
BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW // DISTANCE_DISPLAY_CONDITION_INDEX
];
var that = this;
this._uniforms = {
u_atlas : function() {
return that._textureAtlas.texture;
}
};
var scene = this._scene;
if (defined(scene)) {
this._removeCallbackFunc = scene.terrainProviderChanged.addEventListener(function() {
var billboards = this._billboards;
var length = billboards.length;
for (var i=0;ifalse
,
* and explicitly destroy the atlas to avoid attempting to destroy it multiple times.
*
* @memberof BillboardCollection.prototype
* @type {Boolean}
* @private
*
* @example
* // Set destroyTextureAtlas
* // Destroy a billboard collection but not its texture atlas.
*
* var atlas = new TextureAtlas({
* scene : scene,
* images : images
* });
* billboards.textureAtlas = atlas;
* billboards.destroyTextureAtlas = false;
* billboards = billboards.destroy();
* console.log(atlas.isDestroyed()); // False
*/
destroyTextureAtlas : {
get : function() {
return this._destroyTextureAtlas;
},
set : function(value) {
this._destroyTextureAtlas = value;
}
}
});
function destroyBillboards(billboards) {
var length = billboards.length;
for (var i = 0; i < length; ++i) {
if (billboards[i]) {
billboards[i]._destroy();
}
}
}
/**
* Creates and adds a billboard with the specified initial properties to the collection.
* The added billboard is returned so it can be modified or removed from the collection later.
*
* @param {Object}[billboard] A template describing the billboard's properties as shown in Example 1.
* @returns {Billboard} The billboard that was added to the collection.
*
* @performance Calling add
is expected constant time. However, the collection's vertex buffer
* is rewritten - an O(n)
operation that also incurs CPU to GPU overhead. For
* best performance, add as many billboards as possible before calling update
.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Example 1: Add a billboard, specifying all the default values.
* var b = billboards.add({
* show : true,
* position : Cesium.Cartesian3.ZERO,
* pixelOffset : Cesium.Cartesian2.ZERO,
* eyeOffset : Cesium.Cartesian3.ZERO,
* heightReference : Cesium.HeightReference.NONE,
* horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
* verticalOrigin : Cesium.VerticalOrigin.CENTER,
* scale : 1.0,
* image : 'url/to/image',
* imageSubRegion : undefined,
* color : Cesium.Color.WHITE,
* id : undefined,
* rotation : 0.0,
* alignedAxis : Cesium.Cartesian3.ZERO,
* width : undefined,
* height : undefined,
* scaleByDistance : undefined,
* translucencyByDistance : undefined,
* pixelOffsetScaleByDistance : undefined,
* sizeInMeters : false,
* distanceDisplayCondition : undefined
* });
*
* @example
* // Example 2: Specify only the billboard's cartographic position.
* var b = billboards.add({
* position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
* });
*
* @see BillboardCollection#remove
* @see BillboardCollection#removeAll
*/
BillboardCollection.prototype.add = function(billboard) {
var b = new Billboard(billboard, this);
b._index = this._billboards.length;
this._billboards.push(b);
this._createVertexArray = true;
return b;
};
/**
* Removes a billboard from the collection.
*
* @param {Billboard} billboard The billboard to remove.
* @returns {Boolean} true
if the billboard was removed; false
if the billboard was not found in the collection.
*
* @performance Calling remove
is expected constant time. However, the collection's vertex buffer
* is rewritten - an O(n)
operation that also incurs CPU to GPU overhead. For
* best performance, remove as many billboards as possible before calling update
.
* If you intend to temporarily hide a billboard, it is usually more efficient to call
* {@link Billboard#show} instead of removing and re-adding the billboard.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* var b = billboards.add(...);
* billboards.remove(b); // Returns true
*
* @see BillboardCollection#add
* @see BillboardCollection#removeAll
* @see Billboard#show
*/
BillboardCollection.prototype.remove = function(billboard) {
if (this.contains(billboard)) {
this._billboards[billboard._index] = null; // Removed later
this._billboardsRemoved = true;
this._createVertexArray = true;
billboard._destroy();
return true;
}
return false;
};
/**
* Removes all billboards from the collection.
*
* @performance O(n)
. It is more efficient to remove all the billboards
* from a collection and then add new ones than to create a new collection entirely.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* billboards.add(...);
* billboards.add(...);
* billboards.removeAll();
*
* @see BillboardCollection#add
* @see BillboardCollection#remove
*/
BillboardCollection.prototype.removeAll = function() {
destroyBillboards(this._billboards);
this._billboards = [];
this._billboardsToUpdate = [];
this._billboardsToUpdateIndex = 0;
this._billboardsRemoved = false;
this._createVertexArray = true;
};
function removeBillboards(billboardCollection) {
if (billboardCollection._billboardsRemoved) {
billboardCollection._billboardsRemoved = false;
var newBillboards = [];
var billboards = billboardCollection._billboards;
var length = billboards.length;
for (var i = 0, j = 0; i < length; ++i) {
var billboard = billboards[i];
if (billboard) {
billboard._index = j++;
newBillboards.push(billboard);
}
}
billboardCollection._billboards = newBillboards;
}
}
BillboardCollection.prototype._updateBillboard = function(billboard, propertyChanged) {
if (!billboard._dirty) {
this._billboardsToUpdate[this._billboardsToUpdateIndex++] = billboard;
}
++this._propertiesChanged[propertyChanged];
};
/**
* Check whether this collection contains a given billboard.
*
* @param {Billboard} [billboard] The billboard to check for.
* @returns {Boolean} true if this collection contains the billboard, false otherwise.
*
* @see BillboardCollection#get
*/
BillboardCollection.prototype.contains = function(billboard) {
return defined(billboard) && billboard._billboardCollection === this;
};
/**
* Returns the billboard in the collection at the specified index. Indices are zero-based
* and increase as billboards are added. Removing a billboard shifts all billboards after
* it to the left, changing their indices. This function is commonly used with
* {@link BillboardCollection#length} to iterate over all the billboards
* in the collection.
*
* @param {Number} index The zero-based index of the billboard.
* @returns {Billboard} The billboard at the specified index.
*
* @performance Expected constant time. If billboards were removed from the collection and
* {@link BillboardCollection#update} was not called, an implicit O(n)
* operation is performed.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Toggle the show property of every billboard in the collection
* var len = billboards.length;
* for (var i = 0; i < len; ++i) {
* var b = billboards.get(i);
* b.show = !b.show;
* }
*
* @see BillboardCollection#length
*/
BillboardCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
removeBillboards(this);
return this._billboards[index];
};
var getIndexBuffer;
function getIndexBufferBatched(context) {
var sixteenK = 16 * 1024;
var indexBuffer = context.cache.billboardCollection_indexBufferBatched;
if (defined(indexBuffer)) {
return indexBuffer;
}
// Subtract 6 because the last index is reserverd for primitive restart.
// https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.18
var length = sixteenK * 6 - 6;
var indices = new Uint16Array(length);
for (var i = 0, j = 0; i < length; i += 6, j += 4) {
indices[i] = j;
indices[i + 1] = j + 1;
indices[i + 2] = j + 2;
indices[i + 3] = j + 0;
indices[i + 4] = j + 2;
indices[i + 5] = j + 3;
}
// PERFORMANCE_IDEA: Should we reference count billboard collections, and eventually delete this?
// Is this too much memory to allocate up front? Should we dynamically grow it?
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : indices,
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
indexBuffer.vertexArrayDestroyable = false;
context.cache.billboardCollection_indexBufferBatched = indexBuffer;
return indexBuffer;
}
function getIndexBufferInstanced(context) {
var indexBuffer = context.cache.billboardCollection_indexBufferInstanced;
if (defined(indexBuffer)) {
return indexBuffer;
}
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : new Uint16Array([0, 1, 2, 0, 2, 3]),
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
indexBuffer.vertexArrayDestroyable = false;
context.cache.billboardCollection_indexBufferInstanced = indexBuffer;
return indexBuffer;
}
function getVertexBufferInstanced(context) {
var vertexBuffer = context.cache.billboardCollection_vertexBufferInstanced;
if (defined(vertexBuffer)) {
return vertexBuffer;
}
vertexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : new Float32Array([0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]),
usage : BufferUsage.STATIC_DRAW
});
vertexBuffer.vertexArrayDestroyable = false;
context.cache.billboardCollection_vertexBufferInstanced = vertexBuffer;
return vertexBuffer;
}
BillboardCollection.prototype.computeNewBuffersUsage = function() {
var buffersUsage = this._buffersUsage;
var usageChanged = false;
var properties = this._propertiesChanged;
for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
var newUsage = (properties[k] === 0) ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
usageChanged = usageChanged || (buffersUsage[k] !== newUsage);
buffersUsage[k] = newUsage;
}
return usageChanged;
};
function createVAF(context, numberOfBillboards, buffersUsage, instanced) {
var attributes = [{
index : attributeLocations.positionHighAndScale,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[POSITION_INDEX]
}, {
index : attributeLocations.positionLowAndRotation,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[POSITION_INDEX]
}, {
index : attributeLocations.compressedAttribute0,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[PIXEL_OFFSET_INDEX]
}, {
index : attributeLocations.compressedAttribute1,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.compressedAttribute2,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[COLOR_INDEX]
}, {
index : attributeLocations.eyeOffset,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[EYE_OFFSET_INDEX]
}, {
index : attributeLocations.scaleByDistance,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.pixelOffsetScaleByDistance,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.distanceDisplayCondition,
componentsPerAttribute : 2,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX]
}];
// Instancing requires one non-instanced attribute.
if (instanced) {
attributes.push({
index : attributeLocations.direction,
componentsPerAttribute : 2,
componentDatatype : ComponentDatatype.FLOAT,
vertexBuffer : getVertexBufferInstanced(context)
});
}
// When instancing is enabled, only one vertex is needed for each billboard.
var sizeInVertices = instanced ? numberOfBillboards : 4 * numberOfBillboards;
return new VertexArrayFacade(context, attributes, sizeInVertices, instanced);
}
///////////////////////////////////////////////////////////////////////////
// Four vertices per billboard. Each has the same position, etc., but a different screen-space direction vector.
// PERFORMANCE_IDEA: Save memory if a property is the same for all billboards, use a latched attribute state,
// instead of storing it in a vertex buffer.
var writePositionScratch = new EncodedCartesian3();
function writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var positionHighWriter = vafWriters[attributeLocations.positionHighAndScale];
var positionLowWriter = vafWriters[attributeLocations.positionLowAndRotation];
var position = billboard._getActualPosition();
if (billboardCollection._mode === SceneMode.SCENE3D) {
BoundingSphere.expand(billboardCollection._baseVolume, position, billboardCollection._baseVolume);
billboardCollection._boundingVolumeDirty = true;
}
EncodedCartesian3.fromCartesian(position, writePositionScratch);
var scale = billboard.scale;
var rotation = billboard.rotation;
if (rotation !== 0.0) {
billboardCollection._shaderRotation = true;
}
billboardCollection._maxScale = Math.max(billboardCollection._maxScale, scale);
var high = writePositionScratch.high;
var low = writePositionScratch.low;
if (billboardCollection._instanced) {
i = billboard._index;
positionHighWriter(i, high.x, high.y, high.z, scale);
positionLowWriter(i, low.x, low.y, low.z, rotation);
} else {
i = billboard._index * 4;
positionHighWriter(i + 0, high.x, high.y, high.z, scale);
positionHighWriter(i + 1, high.x, high.y, high.z, scale);
positionHighWriter(i + 2, high.x, high.y, high.z, scale);
positionHighWriter(i + 3, high.x, high.y, high.z, scale);
positionLowWriter(i + 0, low.x, low.y, low.z, rotation);
positionLowWriter(i + 1, low.x, low.y, low.z, rotation);
positionLowWriter(i + 2, low.x, low.y, low.z, rotation);
positionLowWriter(i + 3, low.x, low.y, low.z, rotation);
}
}
var scratchCartesian2 = new Cartesian2();
var UPPER_BOUND = 32768.0; // 2^15
var LEFT_SHIFT16 = 65536.0; // 2^16
var LEFT_SHIFT8 = 256.0; // 2^8
var LEFT_SHIFT7 = 128.0;
var LEFT_SHIFT5 = 32.0;
var LEFT_SHIFT3 = 8.0;
var LEFT_SHIFT2 = 4.0;
var RIGHT_SHIFT8 = 1.0 / 256.0;
var LOWER_LEFT = 0.0;
var LOWER_RIGHT = 2.0;
var UPPER_RIGHT = 3.0;
var UPPER_LEFT = 1.0;
function writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute0];
var pixelOffset = billboard.pixelOffset;
var pixelOffsetX = pixelOffset.x;
var pixelOffsetY = pixelOffset.y;
var translate = billboard._translate;
var translateX = translate.x;
var translateY = translate.y;
billboardCollection._maxPixelOffset = Math.max(billboardCollection._maxPixelOffset, Math.abs(pixelOffsetX + translateX), Math.abs(-pixelOffsetY + translateY));
var horizontalOrigin = billboard.horizontalOrigin;
var verticalOrigin = billboard._verticalOrigin;
var show = billboard.show && billboard.clusterShow;
// If the color alpha is zero, do not show this billboard. This lets us avoid providing
// color during the pick pass and also eliminates a discard in the fragment shader.
if (billboard.color.alpha === 0.0) {
show = false;
}
// Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that.
if (verticalOrigin === VerticalOrigin.BASELINE) {
verticalOrigin = VerticalOrigin.BOTTOM;
}
billboardCollection._allHorizontalCenter = billboardCollection._allHorizontalCenter && horizontalOrigin === HorizontalOrigin.CENTER;
billboardCollection._allVerticalCenter = billboardCollection._allVerticalCenter && verticalOrigin === VerticalOrigin.CENTER;
var bottomLeftX = 0;
var bottomLeftY = 0;
var width = 0;
var height = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
bottomLeftX = imageRectangle.x;
bottomLeftY = imageRectangle.y;
width = imageRectangle.width;
height = imageRectangle.height;
}
var topRightX = bottomLeftX + width;
var topRightY = bottomLeftY + height;
var compressed0 = Math.floor(CesiumMath.clamp(pixelOffsetX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT7;
compressed0 += (horizontalOrigin + 1.0) * LEFT_SHIFT5;
compressed0 += (verticalOrigin + 1.0) * LEFT_SHIFT3;
compressed0 += (show ? 1.0 : 0.0) * LEFT_SHIFT2;
var compressed1 = Math.floor(CesiumMath.clamp(pixelOffsetY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
var compressed2 = Math.floor(CesiumMath.clamp(translateX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
var tempTanslateY = (CesiumMath.clamp(translateY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * RIGHT_SHIFT8;
var upperTranslateY = Math.floor(tempTanslateY);
var lowerTranslateY = Math.floor((tempTanslateY - upperTranslateY) * LEFT_SHIFT8);
compressed1 += upperTranslateY;
compressed2 += lowerTranslateY;
scratchCartesian2.x = bottomLeftX;
scratchCartesian2.y = bottomLeftY;
var compressedTexCoordsLL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.x = topRightX;
var compressedTexCoordsLR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.y = topRightY;
var compressedTexCoordsUR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
scratchCartesian2.x = bottomLeftX;
var compressedTexCoordsUL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL);
} else {
i = billboard._index * 4;
writer(i + 0, compressed0 + LOWER_LEFT, compressed1, compressed2, compressedTexCoordsLL);
writer(i + 1, compressed0 + LOWER_RIGHT, compressed1, compressed2, compressedTexCoordsLR);
writer(i + 2, compressed0 + UPPER_RIGHT, compressed1, compressed2, compressedTexCoordsUR);
writer(i + 3, compressed0 + UPPER_LEFT, compressed1, compressed2, compressedTexCoordsUL);
}
}
function writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute1];
var alignedAxis = billboard.alignedAxis;
if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) {
billboardCollection._shaderAlignedAxis = true;
}
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var translucency = billboard.translucencyByDistance;
if (defined(translucency)) {
near = translucency.near;
nearValue = translucency.nearValue;
far = translucency.far;
farValue = translucency.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// translucency by distance calculation in shader need not be enabled
// until a billboard with near and far !== 1.0 is found
billboardCollection._shaderTranslucencyByDistance = true;
}
}
var width = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
width = imageRectangle.width;
}
var textureWidth = billboardCollection._textureAtlas.texture.width;
var imageWidth = Math.round(defaultValue(billboard.width, textureWidth * width));
billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth);
var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16);
var compressed1 = 0.0;
if (Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) < CesiumMath.EPSILON6) {
compressed1 = AttributeCompression.octEncodeFloat(alignedAxis);
}
nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
compressed0 = compressed0 * LEFT_SHIFT8 + nearValue;
farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
compressed1 = compressed1 * LEFT_SHIFT8 + farValue;
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, near, far);
} else {
i = billboard._index * 4;
writer(i + 0, compressed0, compressed1, near, far);
writer(i + 1, compressed0, compressed1, near, far);
writer(i + 2, compressed0, compressed1, near, far);
writer(i + 3, compressed0, compressed1, near, far);
}
}
function writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.compressedAttribute2];
var color = billboard.color;
var pickColor = billboard.getPickId(context).color;
var sizeInMeters = billboard.sizeInMeters ? 1.0 : 0.0;
var validAlignedAxis = Math.abs(Cartesian3.magnitudeSquared(billboard.alignedAxis) - 1.0) < CesiumMath.EPSILON6 ? 1.0 : 0.0;
billboardCollection._allSizedInMeters = billboardCollection._allSizedInMeters && sizeInMeters === 1.0;
var height = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
height = imageRectangle.height;
}
var dimensions = billboardCollection._textureAtlas.texture.dimensions;
var imageHeight = Math.round(defaultValue(billboard.height, dimensions.y * height));
billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight);
var red = Color.floatToByte(color.red);
var green = Color.floatToByte(color.green);
var blue = Color.floatToByte(color.blue);
var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
red = Color.floatToByte(pickColor.red);
green = Color.floatToByte(pickColor.green);
blue = Color.floatToByte(pickColor.blue);
var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT16 + Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8;
compressed2 += sizeInMeters * 2.0 + validAlignedAxis;
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, compressed0, compressed1, compressed2, imageHeight);
} else {
i = billboard._index * 4;
writer(i + 0, compressed0, compressed1, compressed2, imageHeight);
writer(i + 1, compressed0, compressed1, compressed2, imageHeight);
writer(i + 2, compressed0, compressed1, compressed2, imageHeight);
writer(i + 3, compressed0, compressed1, compressed2, imageHeight);
}
}
function writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.eyeOffset];
var eyeOffset = billboard.eyeOffset;
// For billboards that are clamped to ground, move it slightly closer to the camera
var eyeOffsetZ = eyeOffset.z;
if (billboard._heightReference !== HeightReference.NONE) {
eyeOffsetZ *= 1.005;
}
billboardCollection._maxEyeOffset = Math.max(billboardCollection._maxEyeOffset, Math.abs(eyeOffset.x), Math.abs(eyeOffset.y), Math.abs(eyeOffsetZ));
if (billboardCollection._instanced) {
var width = 0;
var height = 0;
var index = billboard._imageIndex;
if (index !== -1) {
var imageRectangle = textureAtlasCoordinates[index];
if (!defined(imageRectangle)) {
throw new DeveloperError('Invalid billboard image index: ' + index);
}
width = imageRectangle.width;
height = imageRectangle.height;
}
scratchCartesian2.x = width;
scratchCartesian2.y = height;
var compressedTexCoordsRange = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
i = billboard._index;
writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange);
} else {
i = billboard._index * 4;
writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
writer(i + 1, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
writer(i + 2, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
writer(i + 3, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
}
}
function writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.scaleByDistance];
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var scale = billboard.scaleByDistance;
if (defined(scale)) {
near = scale.near;
nearValue = scale.nearValue;
far = scale.far;
farValue = scale.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// scale by distance calculation in shader need not be enabled
// until a billboard with near and far !== 1.0 is found
billboardCollection._shaderScaleByDistance = true;
}
}
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, near, nearValue, far, farValue);
} else {
i = billboard._index * 4;
writer(i + 0, near, nearValue, far, farValue);
writer(i + 1, near, nearValue, far, farValue);
writer(i + 2, near, nearValue, far, farValue);
writer(i + 3, near, nearValue, far, farValue);
}
}
function writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance];
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var pixelOffsetScale = billboard.pixelOffsetScaleByDistance;
if (defined(pixelOffsetScale)) {
near = pixelOffsetScale.near;
nearValue = pixelOffsetScale.nearValue;
far = pixelOffsetScale.far;
farValue = pixelOffsetScale.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// pixelOffsetScale by distance calculation in shader need not be enabled
// until a billboard with near and far !== 1.0 is found
billboardCollection._shaderPixelOffsetScaleByDistance = true;
}
}
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, near, nearValue, far, farValue);
} else {
i = billboard._index * 4;
writer(i + 0, near, nearValue, far, farValue);
writer(i + 1, near, nearValue, far, farValue);
writer(i + 2, near, nearValue, far, farValue);
writer(i + 3, near, nearValue, far, farValue);
}
}
function writeDistanceDisplayCondition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
var i;
var writer = vafWriters[attributeLocations.distanceDisplayCondition];
var near = 0.0;
var far = Number.MAX_VALUE;
var distanceDisplayCondition = billboard.distanceDisplayCondition;
if (defined(distanceDisplayCondition)) {
near = distanceDisplayCondition.near;
far = distanceDisplayCondition.far;
billboardCollection._shaderDistanceDisplayCondition = true;
}
if (billboardCollection._instanced) {
i = billboard._index;
writer(i, near, far);
} else {
i = billboard._index * 4;
writer(i + 0, near, far);
writer(i + 1, near, far);
writer(i + 2, near, far);
writer(i + 3, near, far);
}
}
function writeBillboard(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
writeDistanceDisplayCondition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
}
function recomputeActualPositions(billboardCollection, billboards, length, frameState, modelMatrix, recomputeBoundingVolume) {
var boundingVolume;
if (frameState.mode === SceneMode.SCENE3D) {
boundingVolume = billboardCollection._baseVolume;
billboardCollection._boundingVolumeDirty = true;
} else {
boundingVolume = billboardCollection._baseVolume2D;
}
var positions = [];
for ( var i = 0; i < length; ++i) {
var billboard = billboards[i];
var position = billboard.position;
var actualPosition = Billboard._computeActualPosition(billboard, position, frameState, modelMatrix);
if (defined(actualPosition)) {
billboard._setActualPosition(actualPosition);
if (recomputeBoundingVolume) {
positions.push(actualPosition);
} else {
BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume);
}
}
}
if (recomputeBoundingVolume) {
BoundingSphere.fromPoints(positions, boundingVolume);
}
}
function updateMode(billboardCollection, frameState) {
var mode = frameState.mode;
var billboards = billboardCollection._billboards;
var billboardsToUpdate = billboardCollection._billboardsToUpdate;
var modelMatrix = billboardCollection._modelMatrix;
if (billboardCollection._createVertexArray ||
billboardCollection._mode !== mode ||
mode !== SceneMode.SCENE3D &&
!Matrix4.equals(modelMatrix, billboardCollection.modelMatrix)) {
billboardCollection._mode = mode;
Matrix4.clone(billboardCollection.modelMatrix, modelMatrix);
billboardCollection._createVertexArray = true;
if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true);
}
} else if (mode === SceneMode.MORPHING) {
recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true);
} else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
recomputeActualPositions(billboardCollection, billboardsToUpdate, billboardCollection._billboardsToUpdateIndex, frameState, modelMatrix, false);
}
}
function updateBoundingVolume(collection, frameState, boundingVolume) {
var pixelScale = 1.0;
if (!collection._allSizedInMeters || collection._maxPixelOffset !== 0.0) {
pixelScale = frameState.camera.getPixelSize(boundingVolume, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
}
var size = pixelScale * collection._maxScale * collection._maxSize * 2.0;
if (collection._allHorizontalCenter && collection._allVerticalCenter ) {
size *= 0.5;
}
var offset = pixelScale * collection._maxPixelOffset + collection._maxEyeOffset;
boundingVolume.radius += size + offset;
}
var scratchWriterArray = [];
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {RuntimeError} image with id must be in the atlas.
*/
BillboardCollection.prototype.update = function(frameState) {
removeBillboards(this);
var billboards = this._billboards;
var billboardsLength = billboards.length;
var context = frameState.context;
this._instanced = context.instancedArrays;
attributeLocations = this._instanced ? attributeLocationsInstanced : attributeLocationsBatched;
getIndexBuffer = this._instanced ? getIndexBufferInstanced : getIndexBufferBatched;
var textureAtlas = this._textureAtlas;
if (!defined(textureAtlas)) {
textureAtlas = this._textureAtlas = new TextureAtlas({
context : context
});
for (var ii = 0; ii < billboardsLength; ++ii) {
billboards[ii]._loadImage();
}
}
var textureAtlasCoordinates = textureAtlas.textureCoordinates;
if (textureAtlasCoordinates.length === 0) {
// Can't write billboard vertices until we have texture coordinates
// provided by a texture atlas
return;
}
updateMode(this, frameState);
billboards = this._billboards;
billboardsLength = billboards.length;
var billboardsToUpdate = this._billboardsToUpdate;
var billboardsToUpdateLength = this._billboardsToUpdateIndex;
var properties = this._propertiesChanged;
var textureAtlasGUID = textureAtlas.guid;
var createVertexArray = this._createVertexArray || this._textureAtlasGUID !== textureAtlasGUID;
this._textureAtlasGUID = textureAtlasGUID;
var vafWriters;
var pass = frameState.passes;
var picking = pass.pick;
// PERFORMANCE_IDEA: Round robin multiple buffers.
if (createVertexArray || (!picking && this.computeNewBuffersUsage())) {
this._createVertexArray = false;
for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
properties[k] = 0;
}
this._vaf = this._vaf && this._vaf.destroy();
if (billboardsLength > 0) {
// PERFORMANCE_IDEA: Instead of creating a new one, resize like std::vector.
this._vaf = createVAF(context, billboardsLength, this._buffersUsage, this._instanced);
vafWriters = this._vaf.writers;
// Rewrite entire buffer if billboards were added or removed.
for (var i = 0; i < billboardsLength; ++i) {
var billboard = this._billboards[i];
billboard._dirty = false; // In case it needed an update.
writeBillboard(this, context, textureAtlasCoordinates, vafWriters, billboard);
}
// Different billboard collections share the same index buffer.
this._vaf.commit(getIndexBuffer(context));
}
this._billboardsToUpdateIndex = 0;
} else {
// Billboards were modified, but none were added or removed.
if (billboardsToUpdateLength > 0) {
var writers = scratchWriterArray;
writers.length = 0;
if (properties[POSITION_INDEX] || properties[ROTATION_INDEX] || properties[SCALE_INDEX]) {
writers.push(writePositionScaleAndRotation);
}
if (properties[IMAGE_INDEX_INDEX] || properties[PIXEL_OFFSET_INDEX] || properties[HORIZONTAL_ORIGIN_INDEX] || properties[VERTICAL_ORIGIN_INDEX] || properties[SHOW_INDEX]) {
writers.push(writeCompressedAttrib0);
if (this._instanced) {
writers.push(writeEyeOffset);
}
}
if (properties[IMAGE_INDEX_INDEX] || properties[ALIGNED_AXIS_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) {
writers.push(writeCompressedAttrib1);
writers.push(writeCompressedAttrib2);
}
if (properties[IMAGE_INDEX_INDEX] || properties[COLOR_INDEX]) {
writers.push(writeCompressedAttrib2);
}
if (properties[EYE_OFFSET_INDEX]) {
writers.push(writeEyeOffset);
}
if (properties[SCALE_BY_DISTANCE_INDEX]) {
writers.push(writeScaleByDistance);
}
if (properties[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]) {
writers.push(writePixelOffsetScaleByDistance);
}
if (properties[DISTANCE_DISPLAY_CONDITION_INDEX]) {
writers.push(writeDistanceDisplayCondition);
}
var numWriters = writers.length;
vafWriters = this._vaf.writers;
if ((billboardsToUpdateLength / billboardsLength) > 0.1) {
// If more than 10% of billboard change, rewrite the entire buffer.
// PERFORMANCE_IDEA: I totally made up 10% :).
for (var m = 0; m < billboardsToUpdateLength; ++m) {
var b = billboardsToUpdate[m];
b._dirty = false;
for ( var n = 0; n < numWriters; ++n) {
writers[n](this, context, textureAtlasCoordinates, vafWriters, b);
}
}
this._vaf.commit(getIndexBuffer(context));
} else {
for (var h = 0; h < billboardsToUpdateLength; ++h) {
var bb = billboardsToUpdate[h];
bb._dirty = false;
for ( var o = 0; o < numWriters; ++o) {
writers[o](this, context, textureAtlasCoordinates, vafWriters, bb);
}
if (this._instanced) {
this._vaf.subCommit(bb._index, 1);
} else {
this._vaf.subCommit(bb._index * 4, 4);
}
}
this._vaf.endSubCommits();
}
this._billboardsToUpdateIndex = 0;
}
}
// If the number of total billboards ever shrinks considerably
// Truncate billboardsToUpdate so that we free memory that we're
// not going to be using.
if (billboardsToUpdateLength > billboardsLength * 1.5) {
billboardsToUpdate.length = billboardsLength;
}
if (!defined(this._vaf) || !defined(this._vaf.va)) {
return;
}
if (this._boundingVolumeDirty) {
this._boundingVolumeDirty = false;
BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC);
}
var boundingVolume;
var modelMatrix = Matrix4.IDENTITY;
if (frameState.mode === SceneMode.SCENE3D) {
modelMatrix = this.modelMatrix;
boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume);
} else {
boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume);
}
updateBoundingVolume(this, frameState, boundingVolume);
var va;
var vaLength;
var command;
var vs;
var fs;
var j;
var commandList = frameState.commandList;
if (pass.render) {
var colorList = this._colorCommands;
if (!defined(this._rs)) {
this._rs = RenderState.fromCache({
depthTest : {
enabled : true,
func : WebGLConstants.LEQUAL // Allows label glyphs and billboards to overlap.
},
blending : BlendingState.ALPHA_BLEND
});
}
if (!defined(this._sp) ||
(this._shaderRotation !== this._compiledShaderRotation) ||
(this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) ||
(this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) ||
(this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) ||
(this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) ||
(this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition)) {
vs = new ShaderSource({
sources : [BillboardCollectionVS]
});
if (this._instanced) {
vs.defines.push('INSTANCED');
}
if (this._shaderRotation) {
vs.defines.push('ROTATION');
}
if (this._shaderAlignedAxis) {
vs.defines.push('ALIGNED_AXIS');
}
if (this._shaderScaleByDistance) {
vs.defines.push('EYE_DISTANCE_SCALING');
}
if (this._shaderTranslucencyByDistance) {
vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
}
if (this._shaderPixelOffsetScaleByDistance) {
vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET');
}
if (this._shaderDistanceDisplayCondition) {
vs.defines.push('DISTANCE_DISPLAY_CONDITION');
}
this._sp = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._sp,
vertexShaderSource : vs,
fragmentShaderSource : BillboardCollectionFS,
attributeLocations : attributeLocations
});
this._compiledShaderRotation = this._shaderRotation;
this._compiledShaderAlignedAxis = this._shaderAlignedAxis;
this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance;
this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance;
this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition;
}
va = this._vaf.va;
vaLength = va.length;
colorList.length = vaLength;
for (j = 0; j < vaLength; ++j) {
command = colorList[j];
if (!defined(command)) {
command = colorList[j] = new DrawCommand({
pass : Pass.OPAQUE,
owner : this
});
}
command.boundingVolume = boundingVolume;
command.modelMatrix = modelMatrix;
command.count = va[j].indicesCount;
command.shaderProgram = this._sp;
command.uniformMap = this._uniforms;
command.vertexArray = va[j].va;
command.renderState = this._rs;
command.debugShowBoundingVolume = this.debugShowBoundingVolume;
if (this._instanced) {
command.count = 6;
command.instanceCount = billboardsLength;
}
commandList.push(command);
}
}
if (picking) {
var pickList = this._pickCommands;
if (!defined(this._spPick) ||
(this._shaderRotation !== this._compiledShaderRotationPick) ||
(this._shaderAlignedAxis !== this._compiledShaderAlignedAxisPick) ||
(this._shaderScaleByDistance !== this._compiledShaderScaleByDistancePick) ||
(this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistancePick) ||
(this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistancePick) ||
(this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayConditionPick)) {
vs = new ShaderSource({
defines : ['RENDER_FOR_PICK'],
sources : [BillboardCollectionVS]
});
if(this._instanced) {
vs.defines.push('INSTANCED');
}
if (this._shaderRotation) {
vs.defines.push('ROTATION');
}
if (this._shaderAlignedAxis) {
vs.defines.push('ALIGNED_AXIS');
}
if (this._shaderScaleByDistance) {
vs.defines.push('EYE_DISTANCE_SCALING');
}
if (this._shaderTranslucencyByDistance) {
vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
}
if (this._shaderPixelOffsetScaleByDistance) {
vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET');
}
if (this._shaderDistanceDisplayCondition) {
vs.defines.push('DISTANCE_DISPLAY_CONDITION');
}
fs = new ShaderSource({
defines : ['RENDER_FOR_PICK'],
sources : [BillboardCollectionFS]
});
this._spPick = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._spPick,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
this._compiledShaderRotationPick = this._shaderRotation;
this._compiledShaderAlignedAxisPick = this._shaderAlignedAxis;
this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance;
this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance;
this._compiledShaderPixelOffsetScaleByDistancePick = this._shaderPixelOffsetScaleByDistance;
this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition;
}
va = this._vaf.va;
vaLength = va.length;
pickList.length = vaLength;
for (j = 0; j < vaLength; ++j) {
command = pickList[j];
if (!defined(command)) {
command = pickList[j] = new DrawCommand({
pass : Pass.OPAQUE,
owner : this
});
}
command.boundingVolume = boundingVolume;
command.modelMatrix = modelMatrix;
command.count = va[j].indicesCount;
command.shaderProgram = this._spPick;
command.uniformMap = this._uniforms;
command.vertexArray = va[j].va;
command.renderState = this._rs;
if (this._instanced) {
command.count = 6;
command.instanceCount = billboardsLength;
}
commandList.push(command);
}
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see BillboardCollection#destroy
*/
BillboardCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* billboards = billboards && billboards.destroy();
*
* @see BillboardCollection#isDestroyed
*/
BillboardCollection.prototype.destroy = function() {
if (defined(this._removeCallbackFunc)) {
this._removeCallbackFunc();
this._removeCallbackFunc = undefined;
}
this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy();
this._sp = this._sp && this._sp.destroy();
this._spPick = this._spPick && this._spPick.destroy();
this._vaf = this._vaf && this._vaf.destroy();
destroyBillboards(this._billboards);
return destroyObject(this);
};
return BillboardCollection;
});
/*global define*/
define('Scene/LabelStyle',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Describes how to draw a label.
*
* @exports LabelStyle
*
* @see Label#style
*/
var LabelStyle = {
/**
* Fill the text of the label, but do not outline.
*
* @type {Number}
* @constant
*/
FILL : 0,
/**
* Outline the text of the label, but do not fill.
*
* @type {Number}
* @constant
*/
OUTLINE : 1,
/**
* Fill and outline the text of the label.
*
* @type {Number}
* @constant
*/
FILL_AND_OUTLINE : 2
};
return freezeObject(LabelStyle);
});
/*global define*/
define('Scene/Label',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/NearFarScalar',
'./Billboard',
'./HeightReference',
'./HorizontalOrigin',
'./LabelStyle',
'./VerticalOrigin'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
Color,
defaultValue,
defined,
defineProperties,
DeveloperError,
DistanceDisplayCondition,
NearFarScalar,
Billboard,
HeightReference,
HorizontalOrigin,
LabelStyle,
VerticalOrigin) {
'use strict';
function rebindAllGlyphs(label) {
if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
// only push label if it's not already been marked dirty
label._labelCollection._labelsToUpdate.push(label);
}
label._rebindAllGlyphs = true;
}
function repositionAllGlyphs(label) {
if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
// only push label if it's not already been marked dirty
label._labelCollection._labelsToUpdate.push(label);
}
label._repositionAllGlyphs = true;
}
/**
* A Label draws viewport-aligned text positioned in the 3D scene. This constructor
* should not be used directly, instead create labels by calling {@link LabelCollection#add}.
*
* @alias Label
* @internalConstructor
*
* @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
* @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near
* @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
*
* @see LabelCollection
* @see LabelCollection#add
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
*/
function Label(options, labelCollection) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) {
throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.');
}
if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) {
throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.');
}
if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) {
throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near');
}
this._text = defaultValue(options.text, '');
this._show = defaultValue(options.show, true);
this._font = defaultValue(options.font, '30px sans-serif');
this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE));
this._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.BLACK));
this._outlineWidth = defaultValue(options.outlineWidth, 1.0);
this._showBackground = defaultValue(options.showBackground, false);
this._backgroundColor = defaultValue(options.backgroundColor, new Color(0.165, 0.165, 0.165, 0.8));
this._backgroundPadding = defaultValue(options.backgroundPadding, new Cartesian2(7, 5));
this._style = defaultValue(options.style, LabelStyle.FILL);
this._verticalOrigin = defaultValue(options.verticalOrigin, VerticalOrigin.BASELINE);
this._horizontalOrigin = defaultValue(options.horizontalOrigin, HorizontalOrigin.LEFT);
this._pixelOffset = Cartesian2.clone(defaultValue(options.pixelOffset, Cartesian2.ZERO));
this._eyeOffset = Cartesian3.clone(defaultValue(options.eyeOffset, Cartesian3.ZERO));
this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
this._scale = defaultValue(options.scale, 1.0);
this._id = options.id;
this._translucencyByDistance = options.translucencyByDistance;
this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance;
this._heightReference = defaultValue(options.heightReference, HeightReference.NONE);
this._distanceDisplayCondition = options.distanceDisplayCondition;
this._labelCollection = labelCollection;
this._glyphs = [];
this._backgroundBillboard = undefined;
this._rebindAllGlyphs = true;
this._repositionAllGlyphs = true;
this._actualClampedPosition = undefined;
this._removeCallbackFunc = undefined;
this._mode = undefined;
this._clusterShow = true;
this._updateClamping();
}
defineProperties(Label.prototype, {
/**
* Determines if this label will be shown. Use this to hide or show a label, instead
* of removing it and re-adding it to the collection.
* @memberof Label.prototype
* @type {Boolean}
* @default true
*/
show : {
get : function() {
return this._show;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._show !== value) {
this._show = value;
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var billboard = glyphs[i].billboard;
if (defined(billboard)) {
billboard.show = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.show = value;
}
}
}
},
/**
* Gets or sets the Cartesian position of this label.
* @memberof Label.prototype
* @type {Cartesian3}
*/
position : {
get : function() {
return this._position;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var position = this._position;
if (!Cartesian3.equals(position, value)) {
Cartesian3.clone(value, position);
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var billboard = glyphs[i].billboard;
if (defined(billboard)) {
billboard.position = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.position = value;
}
if (this._heightReference !== HeightReference.NONE) {
this._updateClamping();
}
}
}
},
/**
* Gets or sets the height reference of this billboard.
* @memberof Label.prototype
* @type {HeightReference}
* @default HeightReference.NONE
*/
heightReference : {
get : function() {
return this._heightReference;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (value !== this._heightReference) {
this._heightReference = value;
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var billboard = glyphs[i].billboard;
if (defined(billboard)) {
billboard.heightReference = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.heightReference = value;
}
repositionAllGlyphs(this);
this._updateClamping();
}
}
},
/**
* Gets or sets the text of this label.
* @memberof Label.prototype
* @type {String}
*/
text : {
get : function() {
return this._text;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._text !== value) {
this._text = value;
rebindAllGlyphs(this);
}
}
},
/**
* Gets or sets the font used to draw this label. Fonts are specified using the same syntax as the CSS 'font' property.
* @memberof Label.prototype
* @type {String}
* @default '30px sans-serif'
* @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles|HTML canvas 2D context text styles}
*/
font : {
get : function() {
return this._font;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._font !== value) {
this._font = value;
rebindAllGlyphs(this);
}
}
},
/**
* Gets or sets the fill color of this label.
* @memberof Label.prototype
* @type {Color}
* @default Color.WHITE
* @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
*/
fillColor : {
get : function() {
return this._fillColor;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var fillColor = this._fillColor;
if (!Color.equals(fillColor, value)) {
Color.clone(value, fillColor);
rebindAllGlyphs(this);
}
}
},
/**
* Gets or sets the outline color of this label.
* @memberof Label.prototype
* @type {Color}
* @default Color.BLACK
* @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
*/
outlineColor : {
get : function() {
return this._outlineColor;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var outlineColor = this._outlineColor;
if (!Color.equals(outlineColor, value)) {
Color.clone(value, outlineColor);
rebindAllGlyphs(this);
}
}
},
/**
* Gets or sets the outline width of this label.
* @memberof Label.prototype
* @type {Number}
* @default 1.0
* @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._outlineWidth !== value) {
this._outlineWidth = value;
rebindAllGlyphs(this);
}
}
},
/**
* Determines if a background behind this label will be shown.
* @memberof Label.prototype
* @default false
* @type {Boolean}
*/
showBackground : {
get : function() {
return this._showBackground;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._showBackground !== value) {
this._showBackground = value;
rebindAllGlyphs(this);
}
}
},
/**
* Gets or sets the background color of this label.
* @memberof Label.prototype
* @type {Color}
* @default new Color(0.165, 0.165, 0.165, 0.8)
*/
backgroundColor : {
get : function() {
return this._backgroundColor;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var backgroundColor = this._backgroundColor;
if (!Color.equals(backgroundColor, value)) {
Color.clone(value, backgroundColor);
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.color = backgroundColor;
}
}
}
},
/**
* Gets or sets the background padding, in pixels, of this label. The x
value
* controls horizontal padding, and the y
value controls vertical padding.
* @memberof Label.prototype
* @type {Cartesian2}
* @default new Cartesian2(7, 5)
*/
backgroundPadding : {
get : function() {
return this._backgroundPadding;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var backgroundPadding = this._backgroundPadding;
if (!Cartesian2.equals(backgroundPadding, value)) {
Cartesian2.clone(value, backgroundPadding);
repositionAllGlyphs(this);
}
}
},
/**
* Gets or sets the style of this label.
* @memberof Label.prototype
* @type {LabelStyle}
* @default LabelStyle.FILL
*/
style : {
get : function() {
return this._style;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._style !== value) {
this._style = value;
rebindAllGlyphs(this);
}
}
},
/**
* Gets or sets the pixel offset in screen space from the origin of this label. This is commonly used
* to align multiple labels and billboards at the same position, e.g., an image and text. The
* screen space origin is the top, left corner of the canvas; x
increases from
* left to right, and y
increases from top to bottom.
*
*
*
* default
* l.pixeloffset = new Cartesian2(25, 75);
*
* The label's origin is indicated by the yellow point.
*
* @memberof Label.prototype
* @type {Cartesian2}
* @default Cartesian2.ZERO
*/
pixelOffset : {
get : function() {
return this._pixelOffset;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var pixelOffset = this._pixelOffset;
if (!Cartesian2.equals(pixelOffset, value)) {
Cartesian2.clone(value, pixelOffset);
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.pixelOffset = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.pixelOffset = value;
}
}
}
},
/**
* Gets or sets near and far translucency properties of a Label based on the Label's distance from the camera.
* A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the label's translucency remains clamped to the nearest bound. If undefined,
* translucencyByDistance will be disabled.
* @memberof Label.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a label's translucencyByDistance to 1.0 when the
* // camera is 1500 meters from the label and disappear as
* // the camera distance approaches 8.0e6 meters.
* text.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
*
* @example
* // Example 2.
* // disable translucency by distance
* text.translucencyByDistance = undefined;
*/
translucencyByDistance : {
get : function() {
return this._translucencyByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var translucencyByDistance = this._translucencyByDistance;
if (!NearFarScalar.equals(translucencyByDistance, value)) {
this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance);
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.translucencyByDistance = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.translucencyByDistance = value;
}
}
}
},
/**
* Gets or sets near and far pixel offset scaling properties of a Label based on the Label's distance from the camera.
* A label's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the label's pixel offset scaling remains clamped to the nearest bound. If undefined,
* pixelOffsetScaleByDistance will be disabled.
* @memberof Label.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a label's pixel offset scale to 0.0 when the
* // camera is 1500 meters from the label and scale pixel offset to 10.0 pixels
* // in the y direction the camera distance approaches 8.0e6 meters.
* text.pixelOffset = new Cesium.Cartesian2(0.0, 1.0);
* text.pixelOffsetScaleByDistance = new Cesium.NearFarScalar(1.5e2, 0.0, 8.0e6, 10.0);
*
* @example
* // Example 2.
* // disable pixel offset by distance
* text.pixelOffsetScaleByDistance = undefined;
*/
pixelOffsetScaleByDistance : {
get : function() {
return this._pixelOffsetScaleByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) {
this._pixelOffsetScaleByDistance = NearFarScalar.clone(value, pixelOffsetScaleByDistance);
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.pixelOffsetScaleByDistance = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.pixelOffsetScaleByDistance = value;
}
}
}
},
/**
* Gets and sets the 3D Cartesian offset applied to this label in eye coordinates. Eye coordinates is a left-handed
* coordinate system, where x
points towards the viewer's right, y
points up, and
* z
points into the screen. Eye coordinates use the same scale as world and model coordinates,
* which is typically meters.
*
* An eye offset is commonly used to arrange multiple label or objects at the same position, e.g., to
* arrange a label above its corresponding 3D model.
*
* Below, the label is positioned at the center of the Earth but an eye offset makes it always
* appear on top of the Earth regardless of the viewer's or Earth's orientation.
*
*
*
*
*
*
*
l.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);
*
* @memberof Label.prototype
* @type {Cartesian3}
* @default Cartesian3.ZERO
*/
eyeOffset : {
get : function() {
return this._eyeOffset;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var eyeOffset = this._eyeOffset;
if (!Cartesian3.equals(eyeOffset, value)) {
Cartesian3.clone(value, eyeOffset);
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.eyeOffset = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.eyeOffset = value;
}
}
}
},
/**
* Gets or sets the horizontal origin of this label, which determines if the label is drawn
* to the left, center, or right of its anchor position.
*
*
*
*
* @memberof Label.prototype
* @type {HorizontalOrigin}
* @default HorizontalOrigin.LEFT
* @example
* // Use a top, right origin
* l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT;
* l.verticalOrigin = Cesium.VerticalOrigin.TOP;
*/
horizontalOrigin : {
get : function() {
return this._horizontalOrigin;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._horizontalOrigin !== value) {
this._horizontalOrigin = value;
repositionAllGlyphs(this);
}
}
},
/**
* Gets or sets the vertical origin of this label, which determines if the label is
* to the above, below, or at the center of its anchor position.
*
*
*
*
* @memberof Label.prototype
* @type {VerticalOrigin}
* @default VerticalOrigin.BASELINE
* @example
* // Use a top, right origin
* l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT;
* l.verticalOrigin = Cesium.VerticalOrigin.TOP;
*/
verticalOrigin : {
get : function() {
return this._verticalOrigin;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._verticalOrigin !== value) {
this._verticalOrigin = value;
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.verticalOrigin = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.verticalOrigin = value;
}
repositionAllGlyphs(this);
}
}
},
/**
* Gets or sets the uniform scale that is multiplied with the label's size in pixels.
* A scale of 1.0
does not change the size of the label; a scale greater than
* 1.0
enlarges the label; a positive scale less than 1.0
shrinks
* the label.
*
* Applying a large scale value may pixelate the label. To make text larger without pixelation,
* use a larger font size when calling {@link Label#font} instead.
*
*
*
* From left to right in the above image, the scales are
0.5
,
1.0
,
* and
2.0
.
*
* @memberof Label.prototype
* @type {Number}
* @default 1.0
*/
scale : {
get : function() {
return this._scale;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._scale !== value) {
this._scale = value;
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.scale = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.scale = value;
}
repositionAllGlyphs(this);
}
}
},
/**
* Gets or sets the condition specifying at what distance from the camera that this label will be displayed.
* @memberof Label.prototype
* @type {DistanceDisplayCondition}
* @default undefined
*/
distanceDisplayCondition : {
get : function() {
return this._distanceDisplayCondition;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far must be greater than near');
}
if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.distanceDisplayCondition = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.distanceDisplayCondition = value;
}
}
}
},
/**
* Gets or sets the user-defined object returned when the label is picked.
* @memberof Label.prototype
* @type {Object}
*/
id : {
get : function() {
return this._id;
},
set : function(value) {
if (this._id !== value) {
this._id = value;
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.id = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.id = value;
}
}
}
},
/**
* Keeps track of the position of the label based on the height reference.
* @memberof Label.prototype
* @type {Cartesian3}
* @private
*/
_clampedPosition : {
get : function() {
return this._actualClampedPosition;
},
set : function(value) {
this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition);
var glyphs = this._glyphs;
value = defaultValue(value, this._position);
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
// Set all the private values here, because we already clamped to ground
// so we don't want to do it again for every glyph
glyph.billboard._clampedPosition = value;
Cartesian3.clone(value, glyph.billboard._position);
Cartesian3.clone(value, glyph.billboard._actualPosition);
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard._clampedPosition = value;
Cartesian3.clone(value, backgroundBillboard._position);
Cartesian3.clone(value, backgroundBillboard._actualPosition);
}
}
},
/**
* Determines whether or not this label will be shown or hidden because it was clustered.
* @memberof Label.prototype
* @type {Boolean}
* @default true
* @private
*/
clusterShow : {
get : function() {
return this._clusterShow;
},
set : function(value) {
if (this._clusterShow !== value) {
this._clusterShow = value;
var glyphs = this._glyphs;
for (var i = 0, len = glyphs.length; i < len; i++) {
var glyph = glyphs[i];
if (defined(glyph.billboard)) {
glyph.billboard.clusterShow = value;
}
}
var backgroundBillboard = this._backgroundBillboard;
if (defined(backgroundBillboard)) {
backgroundBillboard.clusterShow = value;
}
}
}
}
});
Label.prototype._updateClamping = function() {
Billboard._updateClamping(this._labelCollection, this);
};
/**
* Computes the screen-space position of the label's origin, taking into account eye and pixel offsets.
* The screen space origin is the top, left corner of the canvas; x
increases from
* left to right, and y
increases from top to bottom.
*
* @param {Scene} scene The scene the label is in.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The screen-space position of the label.
*
*
* @example
* console.log(l.computeScreenSpacePosition(scene).toString());
*
* @see Label#eyeOffset
* @see Label#pixelOffset
*/
Label.prototype.computeScreenSpacePosition = function(scene, result) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(result)) {
result = new Cartesian2();
}
var labelCollection = this._labelCollection;
var modelMatrix = labelCollection.modelMatrix;
var actualPosition = defined(this._actualClampedPosition) ? this._actualClampedPosition : this._position;
var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, actualPosition,
this._eyeOffset, this._pixelOffset, scene, result);
return windowCoordinates;
};
/**
* Gets a label's screen space bounding box centered around screenSpacePosition.
* @param {Label} label The label to get the screen space bounding box for.
* @param {Cartesian2} screenSpacePosition The screen space center of the label.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The screen space bounding box.
*
* @private
*/
Label.getScreenSpaceBoundingBox = function(label, screenSpacePosition, result) {
var x = 0;
var y = 0;
var width = 0;
var height = 0;
var scale = label.scale;
var resolutionScale = label._labelCollection._resolutionScale;
var backgroundBillboard = label._backgroundBillboard;
if (defined(backgroundBillboard)) {
x = screenSpacePosition.x + (backgroundBillboard._translate.x / resolutionScale);
y = screenSpacePosition.y - (backgroundBillboard._translate.y / resolutionScale);
width = backgroundBillboard.width * scale;
height = backgroundBillboard.height * scale;
if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) {
y -= height;
} else if (label.verticalOrigin === VerticalOrigin.CENTER) {
y -= height * 0.5;
}
} else {
x = Number.POSITIVE_INFINITY;
y = Number.POSITIVE_INFINITY;
var maxX = 0;
var maxY = 0;
var glyphs = label._glyphs;
var length = glyphs.length;
for (var i = 0; i < length; ++i) {
var glyph = glyphs[i];
var billboard = glyph.billboard;
if (!defined(billboard)) {
continue;
}
var glyphX = screenSpacePosition.x + (billboard._translate.x / resolutionScale);
var glyphY = screenSpacePosition.y - (billboard._translate.y / resolutionScale);
var glyphWidth = billboard.width * scale;
var glyphHeight = billboard.height * scale;
if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) {
glyphY -= glyphHeight;
} else if (label.verticalOrigin === VerticalOrigin.CENTER) {
glyphY -= glyphHeight * 0.5;
}
x = Math.min(x, glyphX);
y = Math.min(y, glyphY);
maxX = Math.max(maxX, glyphX + glyphWidth);
maxY = Math.max(maxY, glyphY + glyphHeight);
}
width = maxX - x;
height = maxY - y;
}
if (!defined(result)) {
result = new BoundingRectangle();
}
result.x = x;
result.y = y;
result.width = width;
result.height = height;
return result;
};
/**
* Determines if this label equals another label. Labels are equal if all their properties
* are equal. Labels in different collections can be equal.
*
* @param {Label} other The label to compare for equality.
* @returns {Boolean} true
if the labels are equal; otherwise, false
.
*/
Label.prototype.equals = function(other) {
return this === other ||
defined(other) &&
this._show === other._show &&
this._scale === other._scale &&
this._outlineWidth === other._outlineWidth &&
this._showBackground === other._showBackground &&
this._style === other._style &&
this._verticalOrigin === other._verticalOrigin &&
this._horizontalOrigin === other._horizontalOrigin &&
this._heightReference === other._heightReference &&
this._text === other._text &&
this._font === other._font &&
Cartesian3.equals(this._position, other._position) &&
Color.equals(this._fillColor, other._fillColor) &&
Color.equals(this._outlineColor, other._outlineColor) &&
Color.equals(this._backgroundColor, other._backgroundColor) &&
Cartesian2.equals(this._backgroundPadding, other._backgroundPadding) &&
Cartesian2.equals(this._pixelOffset, other._pixelOffset) &&
Cartesian3.equals(this._eyeOffset, other._eyeOffset) &&
NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) &&
NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance) &&
DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition) &&
this._id === other._id;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
Label.prototype.isDestroyed = function() {
return false;
};
return Label;
});
/*global define*/
define('Scene/LabelCollection',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Matrix4',
'../Core/writeTextToCanvas',
'./BillboardCollection',
'./HorizontalOrigin',
'./Label',
'./LabelStyle',
'./TextureAtlas',
'./VerticalOrigin'
], function(
BoundingRectangle,
Cartesian2,
Color,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Matrix4,
writeTextToCanvas,
BillboardCollection,
HorizontalOrigin,
Label,
LabelStyle,
TextureAtlas,
VerticalOrigin) {
'use strict';
// A glyph represents a single character in a particular label. It may or may
// not have a billboard, depending on whether the texture info has an index into
// the the label collection's texture atlas. Invisible characters have no texture, and
// no billboard. However, it always has a valid dimensions object.
function Glyph() {
this.textureInfo = undefined;
this.dimensions = undefined;
this.billboard = undefined;
}
// GlyphTextureInfo represents a single character, drawn in a particular style,
// shared and reference counted across all labels. It may or may not have an
// index into the label collection's texture atlas, depending on whether the character
// has both width and height, but it always has a valid dimensions object.
function GlyphTextureInfo(labelCollection, index, dimensions) {
this.labelCollection = labelCollection;
this.index = index;
this.dimensions = dimensions;
}
// Traditionally, leading is %20 of the font size.
var defaultLineSpacingPercent = 1.2;
var whitePixelCanvasId = 'ID_WHITE_PIXEL';
var whitePixelSize = new Cartesian2(4, 4);
var whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1);
function addWhitePixelCanvas(textureAtlas, labelCollection) {
var canvas = document.createElement('canvas');
canvas.width = whitePixelSize.x;
canvas.height = whitePixelSize.y;
var context2D = canvas.getContext('2d');
context2D.fillStyle = '#fff';
context2D.fillRect(0, 0, canvas.width, canvas.height);
textureAtlas.addImage(whitePixelCanvasId, canvas).then(function(index) {
labelCollection._whitePixelIndex = index;
});
}
// reusable object for calling writeTextToCanvas
var writeTextToCanvasParameters = {};
function createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin) {
writeTextToCanvasParameters.font = font;
writeTextToCanvasParameters.fillColor = fillColor;
writeTextToCanvasParameters.strokeColor = outlineColor;
writeTextToCanvasParameters.strokeWidth = outlineWidth;
if (verticalOrigin === VerticalOrigin.CENTER) {
writeTextToCanvasParameters.textBaseline = 'middle';
} else if (verticalOrigin === VerticalOrigin.TOP) {
writeTextToCanvasParameters.textBaseline = 'top';
} else {
// VerticalOrigin.BOTTOM and VerticalOrigin.BASELINE
writeTextToCanvasParameters.textBaseline = 'bottom';
}
writeTextToCanvasParameters.fill = style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE;
writeTextToCanvasParameters.stroke = style === LabelStyle.OUTLINE || style === LabelStyle.FILL_AND_OUTLINE;
return writeTextToCanvas(character, writeTextToCanvasParameters);
}
function unbindGlyph(labelCollection, glyph) {
glyph.textureInfo = undefined;
glyph.dimensions = undefined;
var billboard = glyph.billboard;
if (defined(billboard)) {
billboard.show = false;
billboard.image = undefined;
labelCollection._spareBillboards.push(billboard);
glyph.billboard = undefined;
}
}
function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) {
textureAtlas.addImage(id, canvas).then(function(index, id) {
glyphTextureInfo.index = index;
});
}
function rebindAllGlyphs(labelCollection, label) {
var text = label._text;
var textLength = text.length;
var glyphs = label._glyphs;
var glyphsLength = glyphs.length;
var glyph;
var glyphIndex;
var textIndex;
// if we have more glyphs than needed, unbind the extras.
if (textLength < glyphsLength) {
for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) {
unbindGlyph(labelCollection, glyphs[glyphIndex]);
}
}
// presize glyphs to match the new text length
glyphs.length = textLength;
var showBackground = label._showBackground && (text.split('\n').join('').length > 0);
var backgroundBillboard = label._backgroundBillboard;
var backgroundBillboardCollection = labelCollection._backgroundBillboardCollection;
if (!showBackground) {
if (defined(backgroundBillboard)) {
backgroundBillboardCollection.remove(backgroundBillboard);
label._backgroundBillboard = backgroundBillboard = undefined;
}
} else {
if (!defined(backgroundBillboard)) {
backgroundBillboard = backgroundBillboardCollection.add({
collection : labelCollection,
image : whitePixelCanvasId,
imageSubRegion : whitePixelBoundingRegion
});
label._backgroundBillboard = backgroundBillboard;
}
backgroundBillboard.color = label._backgroundColor;
backgroundBillboard.show = label._show;
backgroundBillboard.position = label._position;
backgroundBillboard.eyeOffset = label._eyeOffset;
backgroundBillboard.pixelOffset = label._pixelOffset;
backgroundBillboard.horizontalOrigin = HorizontalOrigin.LEFT;
backgroundBillboard.verticalOrigin = label._verticalOrigin;
backgroundBillboard.heightReference = label._heightReference;
backgroundBillboard.scale = label._scale;
backgroundBillboard.pickPrimitive = label;
backgroundBillboard.id = label._id;
backgroundBillboard.translucencyByDistance = label._translucencyByDistance;
backgroundBillboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
backgroundBillboard.distanceDisplayCondition = label._distanceDisplayCondition;
}
var glyphTextureCache = labelCollection._glyphTextureCache;
// walk the text looking for new characters (creating new glyphs for each)
// or changed characters (rebinding existing glyphs)
for (textIndex = 0; textIndex < textLength; ++textIndex) {
var character = text.charAt(textIndex);
var font = label._font;
var fillColor = label._fillColor;
var outlineColor = label._outlineColor;
var outlineWidth = label._outlineWidth;
var style = label._style;
var verticalOrigin = label._verticalOrigin;
// retrieve glyph dimensions and texture index (if the canvas has area)
// from the glyph texture cache, or create and add if not present.
var id = JSON.stringify([
character,
font,
fillColor.toRgba(),
outlineColor.toRgba(),
outlineWidth,
+style,
+verticalOrigin
]);
var glyphTextureInfo = glyphTextureCache[id];
if (!defined(glyphTextureInfo)) {
var canvas = createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin);
glyphTextureInfo = new GlyphTextureInfo(labelCollection, -1, canvas.dimensions);
glyphTextureCache[id] = glyphTextureInfo;
if (canvas.width > 0 && canvas.height > 0) {
addGlyphToTextureAtlas(labelCollection._textureAtlas, id, canvas, glyphTextureInfo);
}
}
glyph = glyphs[textIndex];
if (defined(glyph)) {
// clean up leftover information from the previous glyph
if (glyphTextureInfo.index === -1) {
// no texture, and therefore no billboard, for this glyph.
// so, completely unbind glyph.
unbindGlyph(labelCollection, glyph);
} else {
// we have a texture and billboard. If we had one before, release
// our reference to that texture info, but reuse the billboard.
if (defined(glyph.textureInfo)) {
glyph.textureInfo = undefined;
}
}
} else {
// create a glyph object
glyph = new Glyph();
glyphs[textIndex] = glyph;
}
glyph.textureInfo = glyphTextureInfo;
glyph.dimensions = glyphTextureInfo.dimensions;
// if we have a texture, configure the existing billboard, or obtain one
if (glyphTextureInfo.index !== -1) {
var billboard = glyph.billboard;
var spareBillboards = labelCollection._spareBillboards;
if (!defined(billboard)) {
if (spareBillboards.length > 0) {
billboard = spareBillboards.pop();
} else {
billboard = labelCollection._billboardCollection.add({
collection : labelCollection
});
}
glyph.billboard = billboard;
}
billboard.show = label._show;
billboard.position = label._position;
billboard.eyeOffset = label._eyeOffset;
billboard.pixelOffset = label._pixelOffset;
billboard.horizontalOrigin = HorizontalOrigin.LEFT;
billboard.verticalOrigin = label._verticalOrigin;
billboard.heightReference = label._heightReference;
billboard.scale = label._scale;
billboard.pickPrimitive = label;
billboard.id = label._id;
billboard.image = id;
billboard.translucencyByDistance = label._translucencyByDistance;
billboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
billboard.distanceDisplayCondition = label._distanceDisplayCondition;
}
}
// changing glyphs will cause the position of the
// glyphs to change, since different characters have different widths
label._repositionAllGlyphs = true;
}
function calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding) {
if (horizontalOrigin === HorizontalOrigin.CENTER) {
return -lineWidth / 2;
} else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
return -(lineWidth + backgroundPadding.x);
}
return backgroundPadding.x;
}
// reusable Cartesian2 instances
var glyphPixelOffset = new Cartesian2();
var scratchBackgroundPadding = new Cartesian2();
function repositionAllGlyphs(label, resolutionScale) {
var glyphs = label._glyphs;
var text = label._text;
var glyph;
var dimensions;
var lastLineWidth = 0;
var maxLineWidth = 0;
var lineWidths = [];
var maxGlyphDescent = Number.NEGATIVE_INFINITY;
var maxGlyphY = 0;
var numberOfLines = 1;
var glyphIndex = 0;
var glyphLength = glyphs.length;
var backgroundBillboard = label._backgroundBillboard;
var backgroundPadding = scratchBackgroundPadding;
Cartesian2.clone(
(defined(backgroundBillboard) ? label._backgroundPadding : Cartesian2.ZERO),
backgroundPadding);
for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
if (text.charAt(glyphIndex) === '\n') {
lineWidths.push(lastLineWidth);
++numberOfLines;
lastLineWidth = 0;
} else {
glyph = glyphs[glyphIndex];
dimensions = glyph.dimensions;
maxGlyphY = Math.max(maxGlyphY, dimensions.height - dimensions.descent);
maxGlyphDescent = Math.max(maxGlyphDescent, dimensions.descent);
//Computing the line width must also account for the kerning that occurs between letters.
lastLineWidth += dimensions.width - dimensions.bounds.minx;
if (glyphIndex < glyphLength - 1) {
lastLineWidth += glyphs[glyphIndex + 1].dimensions.bounds.minx;
}
maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
}
}
lineWidths.push(lastLineWidth);
var maxLineHeight = maxGlyphY + maxGlyphDescent;
var scale = label._scale;
var horizontalOrigin = label._horizontalOrigin;
var verticalOrigin = label._verticalOrigin;
var lineIndex = 0;
var lineWidth = lineWidths[lineIndex];
var widthOffset = calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding);
var lineSpacing = defaultLineSpacingPercent * maxLineHeight;
var otherLinesHeight = lineSpacing * (numberOfLines - 1);
glyphPixelOffset.x = widthOffset * scale * resolutionScale;
glyphPixelOffset.y = 0;
var lineOffsetY = 0;
for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
if (text.charAt(glyphIndex) === '\n') {
++lineIndex;
lineOffsetY += lineSpacing;
lineWidth = lineWidths[lineIndex];
widthOffset = calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding);
glyphPixelOffset.x = widthOffset * scale * resolutionScale;
} else {
glyph = glyphs[glyphIndex];
dimensions = glyph.dimensions;
if (verticalOrigin === VerticalOrigin.TOP) {
glyphPixelOffset.y = dimensions.height - maxGlyphY - backgroundPadding.y;
} else if (verticalOrigin === VerticalOrigin.CENTER) {
glyphPixelOffset.y = (otherLinesHeight + dimensions.height - maxGlyphY) / 2;
} else if (verticalOrigin === VerticalOrigin.BASELINE) {
glyphPixelOffset.y = otherLinesHeight;
} else {
// VerticalOrigin.BOTTOM
glyphPixelOffset.y = otherLinesHeight + maxGlyphDescent + backgroundPadding.y;
}
glyphPixelOffset.y = (glyphPixelOffset.y - dimensions.descent - lineOffsetY) * scale * resolutionScale;
if (defined(glyph.billboard)) {
glyph.billboard._setTranslate(glyphPixelOffset);
}
//Compute the next x offset taking into acocunt the kerning performed
//on both the current letter as well as the next letter to be drawn
//as well as any applied scale.
if (glyphIndex < glyphLength - 1) {
var nextGlyph = glyphs[glyphIndex + 1];
glyphPixelOffset.x += ((dimensions.width - dimensions.bounds.minx) + nextGlyph.dimensions.bounds.minx) * scale * resolutionScale;
}
}
}
if (defined(backgroundBillboard) && (text.split('\n').join('').length > 0)) {
if (horizontalOrigin === HorizontalOrigin.CENTER) {
widthOffset = -maxLineWidth / 2 - backgroundPadding.x;
} else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
widthOffset = -(maxLineWidth + backgroundPadding.x * 2);
} else {
widthOffset = 0;
}
glyphPixelOffset.x = widthOffset * scale * resolutionScale;
if (verticalOrigin === VerticalOrigin.TOP) {
glyphPixelOffset.y = maxLineHeight - maxGlyphY - maxGlyphDescent;
} else if (verticalOrigin === VerticalOrigin.CENTER) {
glyphPixelOffset.y = (maxLineHeight - maxGlyphY) / 2 - maxGlyphDescent;
} else if (verticalOrigin === VerticalOrigin.BASELINE) {
glyphPixelOffset.y = -backgroundPadding.y - maxGlyphDescent;
} else {
// VerticalOrigin.BOTTOM
glyphPixelOffset.y = 0;
}
glyphPixelOffset.y = glyphPixelOffset.y * scale * resolutionScale;
backgroundBillboard.width = maxLineWidth + (backgroundPadding.x * 2);
backgroundBillboard.height = maxLineHeight + otherLinesHeight + (backgroundPadding.y * 2);
backgroundBillboard._setTranslate(glyphPixelOffset);
}
}
function destroyLabel(labelCollection, label) {
var glyphs = label._glyphs;
for (var i = 0, len = glyphs.length; i < len; ++i) {
unbindGlyph(labelCollection, glyphs[i]);
}
if (defined(label._backgroundBillboard)) {
labelCollection._backgroundBillboardCollection.remove(label._backgroundBillboard);
label._backgroundBillboard = undefined;
}
label._labelCollection = undefined;
if (defined(label._removeCallbackFunc)) {
label._removeCallbackFunc();
}
destroyObject(label);
}
/**
* A renderable collection of labels. Labels are viewport-aligned text positioned in the 3D scene.
* Each label can have a different font, color, scale, etc.
*
*
*
* Example labels
*
*
* Labels are added and removed from the collection using {@link LabelCollection#add}
* and {@link LabelCollection#remove}.
*
* @alias LabelCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each label from model to world coordinates.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
* @param {Scene} [options.scene] Must be passed in for labels that use the height reference property or will be depth tested against the globe.
*
* @performance For best performance, prefer a few collections, each with many labels, to
* many collections with only a few labels each. Avoid having collections where some
* labels change every frame and others do not; instead, create one or more collections
* for static labels, and one or more collections for dynamic labels.
*
* @see LabelCollection#add
* @see LabelCollection#remove
* @see Label
* @see BillboardCollection
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
*
* @example
* // Create a label collection with two labels
* var labels = scene.primitives.add(new Cesium.LabelCollection());
* labels.add({
* position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
* text : 'A label'
* });
* labels.add({
* position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
* text : 'Another label'
* });
*/
function LabelCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._scene = options.scene;
this._textureAtlas = undefined;
this._backgroundTextureAtlas = undefined;
this._whitePixelIndex = undefined;
this._backgroundBillboardCollection = new BillboardCollection({
scene : this._scene
});
this._backgroundBillboardCollection.destroyTextureAtlas = false;
this._billboardCollection = new BillboardCollection({
scene : this._scene
});
this._billboardCollection.destroyTextureAtlas = false;
this._spareBillboards = [];
this._glyphTextureCache = {};
this._labels = [];
this._labelsToUpdate = [];
this._totalGlyphCount = 0;
this._resolutionScale = undefined;
/**
* The 4x4 transformation matrix that transforms each label in this collection from model to world coordinates.
* When this is the identity matrix, the labels are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type Matrix4
* @default {@link Matrix4.IDENTITY}
*
* @example
* var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
* labels.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
* labels.add({
* position : new Cesium.Cartesian3(0.0, 0.0, 0.0),
* text : 'Center'
* });
* labels.add({
* position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0),
* text : 'East'
* });
* labels.add({
* position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0),
* text : 'North'
* });
* labels.add({
* position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0),
* text : 'Up'
* });
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
}
defineProperties(LabelCollection.prototype, {
/**
* Returns the number of labels in this collection. This is commonly used with
* {@link LabelCollection#get} to iterate over all the labels
* in the collection.
* @memberof LabelCollection.prototype
* @type {Number}
*/
length : {
get : function() {
return this._labels.length;
}
}
});
/**
* Creates and adds a label with the specified initial properties to the collection.
* The added label is returned so it can be modified or removed from the collection later.
*
* @param {Object}[options] A template describing the label's properties as shown in Example 1.
* @returns {Label} The label that was added to the collection.
*
* @performance Calling add
is expected constant time. However, the collection's vertex buffer
* is rewritten; this operations is O(n)
and also incurs
* CPU to GPU overhead. For best performance, add as many billboards as possible before
* calling update
.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Example 1: Add a label, specifying all the default values.
* var l = labels.add({
* show : true,
* position : Cesium.Cartesian3.ZERO,
* text : '',
* font : '30px sans-serif',
* fillColor : Cesium.Color.WHITE,
* outlineColor : Cesium.Color.BLACK,
* outlineWidth : 1.0,
* showBackground : false,
* backgroundColor : new Cesium.Color(0.165, 0.165, 0.165, 0.8),
* backgroundPadding : new Cesium.Cartesian2(7, 5),
* style : Cesium.LabelStyle.FILL,
* pixelOffset : Cesium.Cartesian2.ZERO,
* eyeOffset : Cesium.Cartesian3.ZERO,
* horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
* verticalOrigin : Cesium.VerticalOrigin.BASELINE,
* scale : 1.0,
* translucencyByDistance : undefined,
* pixelOffsetScaleByDistance : undefined,
* heightReference : HeightReference.NONE,
* distanceDisplayCondition : undefined
* });
*
* @example
* // Example 2: Specify only the label's cartographic position,
* // text, and font.
* var l = labels.add({
* position : Cesium.Cartesian3.fromRadians(longitude, latitude, height),
* text : 'Hello World',
* font : '24px Helvetica',
* });
*
* @see LabelCollection#remove
* @see LabelCollection#removeAll
*/
LabelCollection.prototype.add = function(options) {
var label = new Label(options, this);
this._labels.push(label);
this._labelsToUpdate.push(label);
return label;
};
/**
* Removes a label from the collection. Once removed, a label is no longer usable.
*
* @param {Label} label The label to remove.
* @returns {Boolean} true
if the label was removed; false
if the label was not found in the collection.
*
* @performance Calling remove
is expected constant time. However, the collection's vertex buffer
* is rewritten - an O(n)
operation that also incurs CPU to GPU overhead. For
* best performance, remove as many labels as possible before calling update
.
* If you intend to temporarily hide a label, it is usually more efficient to call
* {@link Label#show} instead of removing and re-adding the label.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* var l = labels.add(...);
* labels.remove(l); // Returns true
*
* @see LabelCollection#add
* @see LabelCollection#removeAll
* @see Label#show
*/
LabelCollection.prototype.remove = function(label) {
if (defined(label) && label._labelCollection === this) {
var index = this._labels.indexOf(label);
if (index !== -1) {
this._labels.splice(index, 1);
destroyLabel(this, label);
return true;
}
}
return false;
};
/**
* Removes all labels from the collection.
*
* @performance O(n)
. It is more efficient to remove all the labels
* from a collection and then add new ones than to create a new collection entirely.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* labels.add(...);
* labels.add(...);
* labels.removeAll();
*
* @see LabelCollection#add
* @see LabelCollection#remove
*/
LabelCollection.prototype.removeAll = function() {
var labels = this._labels;
for (var i = 0, len = labels.length; i < len; ++i) {
destroyLabel(this, labels[i]);
}
labels.length = 0;
};
/**
* Check whether this collection contains a given label.
*
* @param {Label} label The label to check for.
* @returns {Boolean} true if this collection contains the label, false otherwise.
*
* @see LabelCollection#get
*/
LabelCollection.prototype.contains = function(label) {
return defined(label) && label._labelCollection === this;
};
/**
* Returns the label in the collection at the specified index. Indices are zero-based
* and increase as labels are added. Removing a label shifts all labels after
* it to the left, changing their indices. This function is commonly used with
* {@link LabelCollection#length} to iterate over all the labels
* in the collection.
*
* @param {Number} index The zero-based index of the billboard.
*
* @returns {Label} The label at the specified index.
*
* @performance Expected constant time. If labels were removed from the collection and
* {@link Scene#render} was not called, an implicit O(n)
* operation is performed.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Toggle the show property of every label in the collection
* var len = labels.length;
* for (var i = 0; i < len; ++i) {
* var l = billboards.get(i);
* l.show = !l.show;
* }
*
* @see LabelCollection#length
*/
LabelCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._labels[index];
};
/**
* @private
*/
LabelCollection.prototype.update = function(frameState) {
var billboardCollection = this._billboardCollection;
var backgroundBillboardCollection = this._backgroundBillboardCollection;
billboardCollection.modelMatrix = this.modelMatrix;
billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
backgroundBillboardCollection.modelMatrix = this.modelMatrix;
backgroundBillboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
var context = frameState.context;
if (!defined(this._textureAtlas)) {
this._textureAtlas = new TextureAtlas({
context : context
});
billboardCollection.textureAtlas = this._textureAtlas;
}
if (!defined(this._backgroundTextureAtlas)) {
this._backgroundTextureAtlas = new TextureAtlas({
context : context,
initialSize : whitePixelSize
});
backgroundBillboardCollection.textureAtlas = this._backgroundTextureAtlas;
addWhitePixelCanvas(this._backgroundTextureAtlas, this);
}
var uniformState = context.uniformState;
var resolutionScale = uniformState.resolutionScale;
var resolutionChanged = this._resolutionScale !== resolutionScale;
this._resolutionScale = resolutionScale;
var labelsToUpdate;
if (resolutionChanged) {
labelsToUpdate = this._labels;
} else {
labelsToUpdate = this._labelsToUpdate;
}
var len = labelsToUpdate.length;
for (var i = 0; i < len; ++i) {
var label = labelsToUpdate[i];
if (label.isDestroyed()) {
continue;
}
var preUpdateGlyphCount = label._glyphs.length;
if (label._rebindAllGlyphs) {
rebindAllGlyphs(this, label);
label._rebindAllGlyphs = false;
}
if (resolutionChanged || label._repositionAllGlyphs) {
repositionAllGlyphs(label, resolutionScale);
label._repositionAllGlyphs = false;
}
var glyphCountDifference = label._glyphs.length - preUpdateGlyphCount;
this._totalGlyphCount += glyphCountDifference;
}
this._labelsToUpdate.length = 0;
backgroundBillboardCollection.update(frameState);
billboardCollection.update(frameState);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see LabelCollection#destroy
*/
LabelCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* labels = labels && labels.destroy();
*
* @see LabelCollection#isDestroyed
*/
LabelCollection.prototype.destroy = function() {
this.removeAll();
this._billboardCollection = this._billboardCollection.destroy();
this._textureAtlas = this._textureAtlas && this._textureAtlas.destroy();
this._backgroundBillboardCollection = this._backgroundBillboardCollection.destroy();
this._backgroundTextureAtlas = this._backgroundTextureAtlas && this._backgroundTextureAtlas.destroy();
return destroyObject(this);
};
return LabelCollection;
});
/*global define*/
define('Scene/PointPrimitive',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/Matrix4',
'../Core/NearFarScalar',
'./SceneMode',
'./SceneTransforms'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
Cartesian4,
Color,
defaultValue,
defined,
defineProperties,
DeveloperError,
DistanceDisplayCondition,
Matrix4,
NearFarScalar,
SceneMode,
SceneTransforms) {
'use strict';
/**
* A graphical point positioned in the 3D scene, that is created
* and rendered using a {@link PointPrimitiveCollection}. A point is created and its initial
* properties are set by calling {@link PointPrimitiveCollection#add}.
*
* @alias PointPrimitive
*
* @performance Reading a property, e.g., {@link PointPrimitive#show}, is constant time.
* Assigning to a property is constant time but results in
* CPU to GPU traffic when {@link PointPrimitiveCollection#update} is called. The per-pointPrimitive traffic is
* the same regardless of how many properties were updated. If most pointPrimitives in a collection need to be
* updated, it may be more efficient to clear the collection with {@link PointPrimitiveCollection#removeAll}
* and add new pointPrimitives instead of modifying each one.
*
* @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near
* @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
* @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
*
* @see PointPrimitiveCollection
* @see PointPrimitiveCollection#add
*
* @internalConstructor
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Points.html|Cesium Sandcastle Points Demo}
*/
function PointPrimitive(options, pointPrimitiveCollection) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) {
throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.');
}
if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) {
throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.');
}
if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) {
throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near');
}
this._show = defaultValue(options.show, true);
this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D
this._color = Color.clone(defaultValue(options.color, Color.WHITE));
this._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.TRANSPARENT));
this._outlineWidth = defaultValue(options.outlineWidth, 0.0);
this._pixelSize = defaultValue(options.pixelSize, 10.0);
this._scaleByDistance = options.scaleByDistance;
this._translucencyByDistance = options.translucencyByDistance;
this._distanceDisplayCondition = options.distanceDisplayCondition;
this._id = options.id;
this._collection = defaultValue(options.collection, pointPrimitiveCollection);
this._clusterShow = true;
this._pickId = undefined;
this._pointPrimitiveCollection = pointPrimitiveCollection;
this._dirty = false;
this._index = -1; //Used only by PointPrimitiveCollection
}
var SHOW_INDEX = PointPrimitive.SHOW_INDEX = 0;
var POSITION_INDEX = PointPrimitive.POSITION_INDEX = 1;
var COLOR_INDEX = PointPrimitive.COLOR_INDEX = 2;
var OUTLINE_COLOR_INDEX = PointPrimitive.OUTLINE_COLOR_INDEX = 3;
var OUTLINE_WIDTH_INDEX = PointPrimitive.OUTLINE_WIDTH_INDEX = 4;
var PIXEL_SIZE_INDEX = PointPrimitive.PIXEL_SIZE_INDEX = 5;
var SCALE_BY_DISTANCE_INDEX = PointPrimitive.SCALE_BY_DISTANCE_INDEX = 6;
var TRANSLUCENCY_BY_DISTANCE_INDEX = PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX = 7;
var DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION = 8;
PointPrimitive.NUMBER_OF_PROPERTIES = 9;
function makeDirty(pointPrimitive, propertyChanged) {
var pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection;
if (defined(pointPrimitiveCollection)) {
pointPrimitiveCollection._updatePointPrimitive(pointPrimitive, propertyChanged);
pointPrimitive._dirty = true;
}
}
defineProperties(PointPrimitive.prototype, {
/**
* Determines if this point will be shown. Use this to hide or show a point, instead
* of removing it and re-adding it to the collection.
* @memberof PointPrimitive.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._show;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._show !== value) {
this._show = value;
makeDirty(this, SHOW_INDEX);
}
}
},
/**
* Gets or sets the Cartesian position of this point.
* @memberof PointPrimitive.prototype
* @type {Cartesian3}
*/
position : {
get : function() {
return this._position;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var position = this._position;
if (!Cartesian3.equals(position, value)) {
Cartesian3.clone(value, position);
Cartesian3.clone(value, this._actualPosition);
makeDirty(this, POSITION_INDEX);
}
}
},
/**
* Gets or sets near and far scaling properties of a point based on the point's distance from the camera.
* A point's scale will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the point's scale remains clamped to the nearest bound. This scale
* multiplies the pixelSize and outlineWidth to affect the total size of the point. If undefined,
* scaleByDistance will be disabled.
* @memberof PointPrimitive.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a pointPrimitive's scaleByDistance to scale to 15 when the
* // camera is 1500 meters from the pointPrimitive and disappear as
* // the camera distance approaches 8.0e6 meters.
* p.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 15, 8.0e6, 0.0);
*
* @example
* // Example 2.
* // disable scaling by distance
* p.scaleByDistance = undefined;
*/
scaleByDistance : {
get : function() {
return this._scaleByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var scaleByDistance = this._scaleByDistance;
if (!NearFarScalar.equals(scaleByDistance, value)) {
this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);
makeDirty(this, SCALE_BY_DISTANCE_INDEX);
}
}
},
/**
* Gets or sets near and far translucency properties of a point based on the point's distance from the camera.
* A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and
* {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
* of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
* Outside of these ranges the point's translucency remains clamped to the nearest bound. If undefined,
* translucencyByDistance will be disabled.
* @memberof PointPrimitive.prototype
* @type {NearFarScalar}
*
* @example
* // Example 1.
* // Set a point's translucency to 1.0 when the
* // camera is 1500 meters from the point and disappear as
* // the camera distance approaches 8.0e6 meters.
* p.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
*
* @example
* // Example 2.
* // disable translucency by distance
* p.translucencyByDistance = undefined;
*/
translucencyByDistance : {
get : function() {
return this._translucencyByDistance;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
var translucencyByDistance = this._translucencyByDistance;
if (!NearFarScalar.equals(translucencyByDistance, value)) {
this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance);
makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX);
}
}
},
/**
* Gets or sets the inner size of the point in pixels.
* @memberof PointPrimitive.prototype
* @type {Number}
*/
pixelSize : {
get : function() {
return this._pixelSize;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._pixelSize !== value) {
this._pixelSize = value;
makeDirty(this, PIXEL_SIZE_INDEX);
}
}
},
/**
* Gets or sets the inner color of the point.
* The red, green, blue, and alpha values are indicated by value
's red
, green
,
* blue
, and alpha
properties as shown in Example 1. These components range from 0.0
* (no intensity) to 1.0
(full intensity).
* @memberof PointPrimitive.prototype
* @type {Color}
*
* @example
* // Example 1. Assign yellow.
* p.color = Cesium.Color.YELLOW;
*
* @example
* // Example 2. Make a pointPrimitive 50% translucent.
* p.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5);
*/
color : {
get : function() {
return this._color;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var color = this._color;
if (!Color.equals(color, value)) {
Color.clone(value, color);
makeDirty(this, COLOR_INDEX);
}
}
},
/**
* Gets or sets the outline color of the point.
* @memberof PointPrimitive.prototype
* @type {Color}
*/
outlineColor : {
get : function() {
return this._outlineColor;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var outlineColor = this._outlineColor;
if (!Color.equals(outlineColor, value)) {
Color.clone(value, outlineColor);
makeDirty(this, OUTLINE_COLOR_INDEX);
}
}
},
/**
* Gets or sets the outline width in pixels. This width adds to pixelSize,
* increasing the total size of the point.
* @memberof PointPrimitive.prototype
* @type {Number}
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (this._outlineWidth !== value) {
this._outlineWidth = value;
makeDirty(this, OUTLINE_WIDTH_INDEX);
}
}
},
/**
* Gets or sets the condition specifying at what distance from the camera that this point will be displayed.
* @memberof PointPrimitive.prototype
* @type {DistanceDisplayCondition}
* @default undefined
*/
distanceDisplayCondition : {
get : function() {
return this._distanceDisplayCondition;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far must be greater than near');
}
if (!DistanceDisplayCondition.equals(this._distanceDisplayCondition, value)) {
this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
makeDirty(this, DISTANCE_DISPLAY_CONDITION_INDEX);
}
}
},
/**
* Gets or sets the user-defined object returned when the point is picked.
* @memberof PointPrimitive.prototype
* @type {Object}
*/
id : {
get : function() {
return this._id;
},
set : function(value) {
this._id = value;
if (defined(this._pickId)) {
this._pickId.object.id = value;
}
}
},
/**
* Determines whether or not this point will be shown or hidden because it was clustered.
* @memberof PointPrimitive.prototype
* @type {Boolean}
* @private
*/
clusterShow : {
get : function() {
return this._clusterShow;
},
set : function(value) {
if (this._clusterShow !== value) {
this._clusterShow = value;
makeDirty(this, SHOW_INDEX);
}
}
}
});
PointPrimitive.prototype.getPickId = function(context) {
if (!defined(this._pickId)) {
this._pickId = context.createPickId({
primitive : this,
collection : this._collection,
id : this._id
});
}
return this._pickId;
};
PointPrimitive.prototype._getActualPosition = function() {
return this._actualPosition;
};
PointPrimitive.prototype._setActualPosition = function(value) {
Cartesian3.clone(value, this._actualPosition);
makeDirty(this, POSITION_INDEX);
};
var tempCartesian3 = new Cartesian4();
PointPrimitive._computeActualPosition = function(position, frameState, modelMatrix) {
if (frameState.mode === SceneMode.SCENE3D) {
return position;
}
Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3);
return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3);
};
var scratchCartesian4 = new Cartesian4();
// This function is basically a stripped-down JavaScript version of PointPrimitiveCollectionVS.glsl
PointPrimitive._computeScreenSpacePosition = function(modelMatrix, position, scene, result) {
// Model to world coordinates
var positionWorld = Matrix4.multiplyByVector(modelMatrix, Cartesian4.fromElements(position.x, position.y, position.z, 1, scratchCartesian4), scratchCartesian4);
var positionWC = SceneTransforms.wgs84ToWindowCoordinates(scene, positionWorld, result);
return positionWC;
};
/**
* Computes the screen-space position of the point's origin.
* The screen space origin is the top, left corner of the canvas; x
increases from
* left to right, and y
increases from top to bottom.
*
* @param {Scene} scene The scene.
* @param {Cartesian2} [result] The object onto which to store the result.
* @returns {Cartesian2} The screen-space position of the point.
*
* @exception {DeveloperError} PointPrimitive must be in a collection.
*
* @example
* console.log(p.computeScreenSpacePosition(scene).toString());
*/
PointPrimitive.prototype.computeScreenSpacePosition = function(scene, result) {
var pointPrimitiveCollection = this._pointPrimitiveCollection;
if (!defined(result)) {
result = new Cartesian2();
}
if (!defined(pointPrimitiveCollection)) {
throw new DeveloperError('PointPrimitive must be in a collection.');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
var modelMatrix = pointPrimitiveCollection.modelMatrix;
var windowCoordinates = PointPrimitive._computeScreenSpacePosition(modelMatrix, this._actualPosition, scene, result);
if (!defined(windowCoordinates)) {
return undefined;
}
windowCoordinates.y = scene.canvas.clientHeight - windowCoordinates.y;
return windowCoordinates;
};
/**
* Gets a point's screen space bounding box centered around screenSpacePosition.
* @param {PointPrimitive} point The point to get the screen space bounding box for.
* @param {Cartesian2} screenSpacePosition The screen space center of the label.
* @param {BoundingRectangle} [result] The object onto which to store the result.
* @returns {BoundingRectangle} The screen space bounding box.
*
* @private
*/
PointPrimitive.getScreenSpaceBoundingBox = function(point, screenSpacePosition, result) {
var size = point.pixelSize;
var halfSize = size * 0.5;
var x = screenSpacePosition.x - halfSize;
var y = screenSpacePosition.y - halfSize;
var width = size;
var height = size;
if (!defined(result)) {
result = new BoundingRectangle();
}
result.x = x;
result.y = y;
result.width = width;
result.height = height;
return result;
};
/**
* Determines if this point equals another point. Points are equal if all their properties
* are equal. Points in different collections can be equal.
*
* @param {PointPrimitive} other The point to compare for equality.
* @returns {Boolean} true
if the points are equal; otherwise, false
.
*/
PointPrimitive.prototype.equals = function(other) {
return this === other ||
defined(other) &&
this._id === other._id &&
Cartesian3.equals(this._position, other._position) &&
Color.equals(this._color, other._color) &&
this._pixelSize === other._pixelSize &&
this._outlineWidth === other._outlineWidth &&
this._show === other._show &&
Color.equals(this._outlineColor, other._outlineColor) &&
NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) &&
DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition);
};
PointPrimitive.prototype._destroy = function() {
this._pickId = this._pickId && this._pickId.destroy();
this._pointPrimitiveCollection = undefined;
};
return PointPrimitive;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PointPrimitiveCollectionFS',[],function() {
'use strict';
return "varying vec4 v_color;\n\
varying vec4 v_outlineColor;\n\
varying float v_innerPercent;\n\
varying float v_pixelDistance;\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#endif\n\
void main()\n\
{\n\
float distanceToCenter = length(gl_PointCoord - vec2(0.5));\n\
float maxDistance = max(0.0, 0.5 - v_pixelDistance);\n\
float wholeAlpha = 1.0 - smoothstep(maxDistance, 0.5, distanceToCenter);\n\
float innerAlpha = 1.0 - smoothstep(maxDistance * v_innerPercent, 0.5 * v_innerPercent, distanceToCenter);\n\
vec4 color = mix(v_outlineColor, v_color, innerAlpha);\n\
color.a *= wholeAlpha;\n\
if (color.a < 0.005)\n\
{\n\
discard;\n\
}\n\
#ifdef RENDER_FOR_PICK\n\
gl_FragColor = v_pickColor;\n\
#else\n\
gl_FragColor = color;\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PointPrimitiveCollectionVS',[],function() {
'use strict';
return "uniform float u_maxTotalPointSize;\n\
attribute vec4 positionHighAndSize;\n\
attribute vec4 positionLowAndOutline;\n\
attribute vec4 compressedAttribute0;\n\
attribute vec4 compressedAttribute1;\n\
attribute vec4 scaleByDistance;\n\
attribute vec2 distanceDisplayCondition;\n\
varying vec4 v_color;\n\
varying vec4 v_outlineColor;\n\
varying float v_innerPercent;\n\
varying float v_pixelDistance;\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#endif\n\
const float SHIFT_LEFT8 = 256.0;\n\
const float SHIFT_RIGHT8 = 1.0 / 256.0;\n\
void main()\n\
{\n\
vec3 positionHigh = positionHighAndSize.xyz;\n\
vec3 positionLow = positionLowAndOutline.xyz;\n\
float outlineWidthBothSides = 2.0 * positionLowAndOutline.w;\n\
float totalSize = positionHighAndSize.w + outlineWidthBothSides;\n\
float outlinePercent = outlineWidthBothSides / totalSize;\n\
totalSize *= czm_resolutionScale;\n\
totalSize += 3.0;\n\
float temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\
float show = floor(temp);\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
vec4 translucencyByDistance;\n\
translucencyByDistance.x = compressedAttribute1.z;\n\
translucencyByDistance.z = compressedAttribute1.w;\n\
translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\
translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
#endif\n\
vec4 color;\n\
vec4 outlineColor;\n\
#ifdef RENDER_FOR_PICK\n\
color = vec4(0.0);\n\
outlineColor = vec4(0.0);\n\
vec4 pickColor;\n\
temp = compressedAttribute0.z * SHIFT_RIGHT8;\n\
pickColor.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
temp = floor(temp) * SHIFT_RIGHT8;\n\
pickColor.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
pickColor.r = floor(temp);\n\
#else\n\
temp = compressedAttribute0.x * SHIFT_RIGHT8;\n\
color.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
temp = floor(temp) * SHIFT_RIGHT8;\n\
color.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
color.r = floor(temp);\n\
temp = compressedAttribute0.y * SHIFT_RIGHT8;\n\
outlineColor.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
temp = floor(temp) * SHIFT_RIGHT8;\n\
outlineColor.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
outlineColor.r = floor(temp);\n\
#endif\n\
temp = compressedAttribute0.w * SHIFT_RIGHT8;\n\
#ifdef RENDER_FOR_PICK\n\
pickColor.a = (temp - floor(temp)) * SHIFT_LEFT8;\n\
pickColor = pickColor / 255.0;\n\
#endif\n\
temp = floor(temp) * SHIFT_RIGHT8;\n\
outlineColor.a = (temp - floor(temp)) * SHIFT_LEFT8;\n\
outlineColor /= 255.0;\n\
color.a = floor(temp);\n\
color /= 255.0;\n\
vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\
vec4 positionEC = czm_modelViewRelativeToEye * p;\n\
positionEC.xyz *= show;\n\
#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(DISTANCE_DISPLAY_CONDITION)\n\
float lengthSq;\n\
if (czm_sceneMode == czm_sceneMode2D)\n\
{\n\
lengthSq = czm_eyeHeight2D.y;\n\
}\n\
else\n\
{\n\
lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\
}\n\
#endif\n\
#ifdef EYE_DISTANCE_SCALING\n\
totalSize *= czm_nearFarScalar(scaleByDistance, lengthSq);\n\
#endif\n\
totalSize = min(totalSize, u_maxTotalPointSize);\n\
if (totalSize < 1.0)\n\
{\n\
positionEC.xyz = vec3(0.0);\n\
totalSize = 1.0;\n\
}\n\
float translucency = 1.0;\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\
if (translucency < 0.004)\n\
{\n\
positionEC.xyz = vec3(0.0);\n\
}\n\
#endif\n\
#ifdef DISTANCE_DISPLAY_CONDITION\n\
float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x;\n\
float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y;\n\
if (lengthSq < nearSq || lengthSq > farSq) {\n\
positionEC.xyz = vec3(0.0);\n\
}\n\
#endif\n\
vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0);\n\
v_color = color;\n\
v_color.a *= translucency;\n\
v_outlineColor = outlineColor;\n\
v_outlineColor.a *= translucency;\n\
v_innerPercent = 1.0 - outlinePercent;\n\
v_pixelDistance = 2.0 / totalSize;\n\
gl_PointSize = totalSize;\n\
#ifdef RENDER_FOR_PICK\n\
v_pickColor = pickColor;\n\
#endif\n\
}\n\
";
});
/*global define*/
define('Scene/PointPrimitiveCollection',[
'../Core/BoundingSphere',
'../Core/Color',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EncodedCartesian3',
'../Core/Math',
'../Core/Matrix4',
'../Core/PrimitiveType',
'../Core/WebGLConstants',
'../Renderer/BufferUsage',
'../Renderer/ContextLimits',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArrayFacade',
'../Shaders/PointPrimitiveCollectionFS',
'../Shaders/PointPrimitiveCollectionVS',
'./BlendingState',
'./PointPrimitive',
'./SceneMode'
], function(
BoundingSphere,
Color,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EncodedCartesian3,
CesiumMath,
Matrix4,
PrimitiveType,
WebGLConstants,
BufferUsage,
ContextLimits,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
ShaderSource,
VertexArrayFacade,
PointPrimitiveCollectionFS,
PointPrimitiveCollectionVS,
BlendingState,
PointPrimitive,
SceneMode) {
'use strict';
var SHOW_INDEX = PointPrimitive.SHOW_INDEX;
var POSITION_INDEX = PointPrimitive.POSITION_INDEX;
var COLOR_INDEX = PointPrimitive.COLOR_INDEX;
var OUTLINE_COLOR_INDEX = PointPrimitive.OUTLINE_COLOR_INDEX;
var OUTLINE_WIDTH_INDEX = PointPrimitive.OUTLINE_WIDTH_INDEX;
var PIXEL_SIZE_INDEX = PointPrimitive.PIXEL_SIZE_INDEX;
var SCALE_BY_DISTANCE_INDEX = PointPrimitive.SCALE_BY_DISTANCE_INDEX;
var TRANSLUCENCY_BY_DISTANCE_INDEX = PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX;
var DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX;
var NUMBER_OF_PROPERTIES = PointPrimitive.NUMBER_OF_PROPERTIES;
var attributeLocations = {
positionHighAndSize : 0,
positionLowAndOutline : 1,
compressedAttribute0 : 2, // color, outlineColor, pick color
compressedAttribute1 : 3, // show, translucency by distance, some free space
scaleByDistance : 4,
distanceDisplayCondition : 5
};
/**
* A renderable collection of points.
*
* Points are added and removed from the collection using {@link PointPrimitiveCollection#add}
* and {@link PointPrimitiveCollection#remove}.
*
* @alias PointPrimitiveCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each point from model to world coordinates.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
*
* @performance For best performance, prefer a few collections, each with many points, to
* many collections with only a few points each. Organize collections so that points
* with the same update frequency are in the same collection, i.e., points that do not
* change should be in one collection; points that change every frame should be in another
* collection; and so on.
*
*
* @example
* // Create a pointPrimitive collection with two points
* var points = scene.primitives.add(new Cesium.PointPrimitiveCollection());
* points.add({
* position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
* color : Cesium.Color.YELLOW
* });
* points.add({
* position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
* color : Cesium.Color.CYAN
* });
*
* @see PointPrimitiveCollection#add
* @see PointPrimitiveCollection#remove
* @see PointPrimitive
*/
function PointPrimitiveCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._sp = undefined;
this._rs = undefined;
this._vaf = undefined;
this._spPick = undefined;
this._pointPrimitives = [];
this._pointPrimitivesToUpdate = [];
this._pointPrimitivesToUpdateIndex = 0;
this._pointPrimitivesRemoved = false;
this._createVertexArray = false;
this._shaderScaleByDistance = false;
this._compiledShaderScaleByDistance = false;
this._compiledShaderScaleByDistancePick = false;
this._shaderTranslucencyByDistance = false;
this._compiledShaderTranslucencyByDistance = false;
this._compiledShaderTranslucencyByDistancePick = false;
this._shaderDistanceDisplayCondition = false;
this._compiledShaderDistanceDisplayCondition = false;
this._compiledShaderDistanceDisplayConditionPick = false;
this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
this._maxPixelSize = 1.0;
this._baseVolume = new BoundingSphere();
this._baseVolumeWC = new BoundingSphere();
this._baseVolume2D = new BoundingSphere();
this._boundingVolume = new BoundingSphere();
this._boundingVolumeDirty = false;
this._colorCommands = [];
this._pickCommands = [];
/**
* The 4x4 transformation matrix that transforms each point in this collection from model to world coordinates.
* When this is the identity matrix, the pointPrimitives are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*
*
* @example
* var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
* pointPrimitives.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
* pointPrimitives.add({
* color : Cesium.Color.ORANGE,
* position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
* });
* pointPrimitives.add({
* color : Cesium.Color.YELLOW,
* position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
* });
* pointPrimitives.add({
* color : Cesium.Color.GREEN,
* position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
* });
* pointPrimitives.add({
* color : Cesium.Color.CYAN,
* position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
* });
*
* @see Transforms.eastNorthUpToFixedFrame
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
this._mode = SceneMode.SCENE3D;
this._maxTotalPointSize = 1;
// The buffer usage for each attribute is determined based on the usage of the attribute over time.
this._buffersUsage = [
BufferUsage.STATIC_DRAW, // SHOW_INDEX
BufferUsage.STATIC_DRAW, // POSITION_INDEX
BufferUsage.STATIC_DRAW, // COLOR_INDEX
BufferUsage.STATIC_DRAW, // OUTLINE_COLOR_INDEX
BufferUsage.STATIC_DRAW, // OUTLINE_WIDTH_INDEX
BufferUsage.STATIC_DRAW, // PIXEL_SIZE_INDEX
BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
BufferUsage.STATIC_DRAW // DISTANCE_DISPLAY_CONDITION_INDEX
];
var that = this;
this._uniforms = {
u_maxTotalPointSize : function() {
return that._maxTotalPointSize;
}
};
}
defineProperties(PointPrimitiveCollection.prototype, {
/**
* Returns the number of points in this collection. This is commonly used with
* {@link PointPrimitiveCollection#get} to iterate over all the points
* in the collection.
* @memberof PointPrimitiveCollection.prototype
* @type {Number}
*/
length : {
get : function() {
removePointPrimitives(this);
return this._pointPrimitives.length;
}
}
});
function destroyPointPrimitives(pointPrimitives) {
var length = pointPrimitives.length;
for (var i = 0; i < length; ++i) {
if (pointPrimitives[i]) {
pointPrimitives[i]._destroy();
}
}
}
/**
* Creates and adds a point with the specified initial properties to the collection.
* The added point is returned so it can be modified or removed from the collection later.
*
* @param {Object}[pointPrimitive] A template describing the point's properties as shown in Example 1.
* @returns {PointPrimitive} The point that was added to the collection.
*
* @performance Calling add
is expected constant time. However, the collection's vertex buffer
* is rewritten - an O(n)
operation that also incurs CPU to GPU overhead. For
* best performance, add as many pointPrimitives as possible before calling update
.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Example 1: Add a point, specifying all the default values.
* var p = pointPrimitives.add({
* show : true,
* position : Cesium.Cartesian3.ZERO,
* pixelSize : 10.0,
* color : Cesium.Color.WHITE,
* outlineColor : Cesium.Color.TRANSPARENT,
* outlineWidth : 0.0,
* id : undefined
* });
*
* @example
* // Example 2: Specify only the point's cartographic position.
* var p = pointPrimitives.add({
* position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
* });
*
* @see PointPrimitiveCollection#remove
* @see PointPrimitiveCollection#removeAll
*/
PointPrimitiveCollection.prototype.add = function(pointPrimitive) {
var p = new PointPrimitive(pointPrimitive, this);
p._index = this._pointPrimitives.length;
this._pointPrimitives.push(p);
this._createVertexArray = true;
return p;
};
/**
* Removes a point from the collection.
*
* @param {PointPrimitive} pointPrimitive The point to remove.
* @returns {Boolean} true
if the point was removed; false
if the point was not found in the collection.
*
* @performance Calling remove
is expected constant time. However, the collection's vertex buffer
* is rewritten - an O(n)
operation that also incurs CPU to GPU overhead. For
* best performance, remove as many points as possible before calling update
.
* If you intend to temporarily hide a point, it is usually more efficient to call
* {@link PointPrimitive#show} instead of removing and re-adding the point.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* var p = pointPrimitives.add(...);
* pointPrimitives.remove(p); // Returns true
*
* @see PointPrimitiveCollection#add
* @see PointPrimitiveCollection#removeAll
* @see PointPrimitive#show
*/
PointPrimitiveCollection.prototype.remove = function(pointPrimitive) {
if (this.contains(pointPrimitive)) {
this._pointPrimitives[pointPrimitive._index] = null; // Removed later
this._pointPrimitivesRemoved = true;
this._createVertexArray = true;
pointPrimitive._destroy();
return true;
}
return false;
};
/**
* Removes all points from the collection.
*
* @performance O(n)
. It is more efficient to remove all the points
* from a collection and then add new ones than to create a new collection entirely.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* pointPrimitives.add(...);
* pointPrimitives.add(...);
* pointPrimitives.removeAll();
*
* @see PointPrimitiveCollection#add
* @see PointPrimitiveCollection#remove
*/
PointPrimitiveCollection.prototype.removeAll = function() {
destroyPointPrimitives(this._pointPrimitives);
this._pointPrimitives = [];
this._pointPrimitivesToUpdate = [];
this._pointPrimitivesToUpdateIndex = 0;
this._pointPrimitivesRemoved = false;
this._createVertexArray = true;
};
function removePointPrimitives(pointPrimitiveCollection) {
if (pointPrimitiveCollection._pointPrimitivesRemoved) {
pointPrimitiveCollection._pointPrimitivesRemoved = false;
var newPointPrimitives = [];
var pointPrimitives = pointPrimitiveCollection._pointPrimitives;
var length = pointPrimitives.length;
for (var i = 0, j = 0; i < length; ++i) {
var pointPrimitive = pointPrimitives[i];
if (pointPrimitive) {
pointPrimitive._index = j++;
newPointPrimitives.push(pointPrimitive);
}
}
pointPrimitiveCollection._pointPrimitives = newPointPrimitives;
}
}
PointPrimitiveCollection.prototype._updatePointPrimitive = function(pointPrimitive, propertyChanged) {
if (!pointPrimitive._dirty) {
this._pointPrimitivesToUpdate[this._pointPrimitivesToUpdateIndex++] = pointPrimitive;
}
++this._propertiesChanged[propertyChanged];
};
/**
* Check whether this collection contains a given point.
*
* @param {PointPrimitive} [pointPrimitive] The point to check for.
* @returns {Boolean} true if this collection contains the point, false otherwise.
*
* @see PointPrimitiveCollection#get
*/
PointPrimitiveCollection.prototype.contains = function(pointPrimitive) {
return defined(pointPrimitive) && pointPrimitive._pointPrimitiveCollection === this;
};
/**
* Returns the point in the collection at the specified index. Indices are zero-based
* and increase as points are added. Removing a point shifts all points after
* it to the left, changing their indices. This function is commonly used with
* {@link PointPrimitiveCollection#length} to iterate over all the points
* in the collection.
*
* @param {Number} index The zero-based index of the point.
* @returns {PointPrimitive} The point at the specified index.
*
* @performance Expected constant time. If points were removed from the collection and
* {@link PointPrimitiveCollection#update} was not called, an implicit O(n)
* operation is performed.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Toggle the show property of every point in the collection
* var len = pointPrimitives.length;
* for (var i = 0; i < len; ++i) {
* var p = pointPrimitives.get(i);
* p.show = !p.show;
* }
*
* @see PointPrimitiveCollection#length
*/
PointPrimitiveCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
removePointPrimitives(this);
return this._pointPrimitives[index];
};
PointPrimitiveCollection.prototype.computeNewBuffersUsage = function() {
var buffersUsage = this._buffersUsage;
var usageChanged = false;
var properties = this._propertiesChanged;
for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
var newUsage = (properties[k] === 0) ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
usageChanged = usageChanged || (buffersUsage[k] !== newUsage);
buffersUsage[k] = newUsage;
}
return usageChanged;
};
function createVAF(context, numberOfPointPrimitives, buffersUsage) {
return new VertexArrayFacade(context, [{
index : attributeLocations.positionHighAndSize,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[POSITION_INDEX]
}, {
index : attributeLocations.positionLowAndShow,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[POSITION_INDEX]
}, {
index : attributeLocations.compressedAttribute0,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[COLOR_INDEX]
}, {
index : attributeLocations.compressedAttribute1,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.scaleByDistance,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
}, {
index : attributeLocations.distanceDisplayCondition,
componentsPerAttribute : 2,
componentDatatype : ComponentDatatype.FLOAT,
usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX]
}], numberOfPointPrimitives); // 1 vertex per pointPrimitive
}
///////////////////////////////////////////////////////////////////////////
// PERFORMANCE_IDEA: Save memory if a property is the same for all pointPrimitives, use a latched attribute state,
// instead of storing it in a vertex buffer.
var writePositionScratch = new EncodedCartesian3();
function writePositionSizeAndOutline(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
var i = pointPrimitive._index;
var position = pointPrimitive._getActualPosition();
if (pointPrimitiveCollection._mode === SceneMode.SCENE3D) {
BoundingSphere.expand(pointPrimitiveCollection._baseVolume, position, pointPrimitiveCollection._baseVolume);
pointPrimitiveCollection._boundingVolumeDirty = true;
}
EncodedCartesian3.fromCartesian(position, writePositionScratch);
var pixelSize = pointPrimitive.pixelSize;
var outlineWidth = pointPrimitive.outlineWidth;
pointPrimitiveCollection._maxPixelSize = Math.max(pointPrimitiveCollection._maxPixelSize, pixelSize + outlineWidth);
var positionHighWriter = vafWriters[attributeLocations.positionHighAndSize];
var high = writePositionScratch.high;
positionHighWriter(i, high.x, high.y, high.z, pixelSize);
var positionLowWriter = vafWriters[attributeLocations.positionLowAndOutline];
var low = writePositionScratch.low;
positionLowWriter(i, low.x, low.y, low.z, outlineWidth);
}
var LEFT_SHIFT16 = 65536.0; // 2^16
var LEFT_SHIFT8 = 256.0; // 2^8
function writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
var i = pointPrimitive._index;
var color = pointPrimitive.color;
var pickColor = pointPrimitive.getPickId(context).color;
var outlineColor = pointPrimitive.outlineColor;
var red = Color.floatToByte(color.red);
var green = Color.floatToByte(color.green);
var blue = Color.floatToByte(color.blue);
var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
red = Color.floatToByte(outlineColor.red);
green = Color.floatToByte(outlineColor.green);
blue = Color.floatToByte(outlineColor.blue);
var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
red = Color.floatToByte(pickColor.red);
green = Color.floatToByte(pickColor.green);
blue = Color.floatToByte(pickColor.blue);
var compressed2 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;
var compressed3 =
Color.floatToByte(color.alpha) * LEFT_SHIFT16 +
Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT8 +
Color.floatToByte(pickColor.alpha);
var writer = vafWriters[attributeLocations.compressedAttribute0];
writer(i, compressed0, compressed1, compressed2, compressed3);
}
function writeCompressedAttrib1(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
var i = pointPrimitive._index;
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var translucency = pointPrimitive.translucencyByDistance;
if (defined(translucency)) {
near = translucency.near;
nearValue = translucency.nearValue;
far = translucency.far;
farValue = translucency.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// translucency by distance calculation in shader need not be enabled
// until a pointPrimitive with near and far !== 1.0 is found
pointPrimitiveCollection._shaderTranslucencyByDistance = true;
}
}
var show = pointPrimitive.show && pointPrimitive.clusterShow;
// If the color alphas are zero, do not show this pointPrimitive. This lets us avoid providing
// color during the pick pass and also eliminates a discard in the fragment shader.
if (pointPrimitive.color.alpha === 0.0 && pointPrimitive.outlineColor.alpha === 0.0) {
show = false;
}
nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
var compressed0 = (show ? 1.0 : 0.0) * LEFT_SHIFT8 + nearValue;
farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
var compressed1 = farValue;
var writer = vafWriters[attributeLocations.compressedAttribute1];
writer(i, compressed0, compressed1, near, far);
}
function writeScaleByDistance(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
var i = pointPrimitive._index;
var writer = vafWriters[attributeLocations.scaleByDistance];
var near = 0.0;
var nearValue = 1.0;
var far = 1.0;
var farValue = 1.0;
var scale = pointPrimitive.scaleByDistance;
if (defined(scale)) {
near = scale.near;
nearValue = scale.nearValue;
far = scale.far;
farValue = scale.farValue;
if (nearValue !== 1.0 || farValue !== 1.0) {
// scale by distance calculation in shader need not be enabled
// until a pointPrimitive with near and far !== 1.0 is found
pointPrimitiveCollection._shaderScaleByDistance = true;
}
}
writer(i, near, nearValue, far, farValue);
}
function writeDistanceDisplayCondition(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
var i = pointPrimitive._index;
var writer = vafWriters[attributeLocations.distanceDisplayCondition];
var near = 0.0;
var far = Number.MAX_VALUE;
var distanceDisplayCondition = pointPrimitive.distanceDisplayCondition;
if (defined(distanceDisplayCondition)) {
near = distanceDisplayCondition.near;
far = distanceDisplayCondition.far;
pointPrimitiveCollection._shaderDistanceDisplayCondition = true;
}
writer(i, near, far);
}
function writePointPrimitive(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
writePositionSizeAndOutline(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
writeCompressedAttrib1(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
writeScaleByDistance(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
writeDistanceDisplayCondition(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
}
function recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, length, frameState, modelMatrix, recomputeBoundingVolume) {
var boundingVolume;
if (frameState.mode === SceneMode.SCENE3D) {
boundingVolume = pointPrimitiveCollection._baseVolume;
pointPrimitiveCollection._boundingVolumeDirty = true;
} else {
boundingVolume = pointPrimitiveCollection._baseVolume2D;
}
var positions = [];
for ( var i = 0; i < length; ++i) {
var pointPrimitive = pointPrimitives[i];
var position = pointPrimitive.position;
var actualPosition = PointPrimitive._computeActualPosition(position, frameState, modelMatrix);
if (defined(actualPosition)) {
pointPrimitive._setActualPosition(actualPosition);
if (recomputeBoundingVolume) {
positions.push(actualPosition);
} else {
BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume);
}
}
}
if (recomputeBoundingVolume) {
BoundingSphere.fromPoints(positions, boundingVolume);
}
}
function updateMode(pointPrimitiveCollection, frameState) {
var mode = frameState.mode;
var pointPrimitives = pointPrimitiveCollection._pointPrimitives;
var pointPrimitivesToUpdate = pointPrimitiveCollection._pointPrimitivesToUpdate;
var modelMatrix = pointPrimitiveCollection._modelMatrix;
if (pointPrimitiveCollection._createVertexArray ||
pointPrimitiveCollection._mode !== mode ||
mode !== SceneMode.SCENE3D &&
!Matrix4.equals(modelMatrix, pointPrimitiveCollection.modelMatrix)) {
pointPrimitiveCollection._mode = mode;
Matrix4.clone(pointPrimitiveCollection.modelMatrix, modelMatrix);
pointPrimitiveCollection._createVertexArray = true;
if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, pointPrimitives.length, frameState, modelMatrix, true);
}
} else if (mode === SceneMode.MORPHING) {
recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, pointPrimitives.length, frameState, modelMatrix, true);
} else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
recomputeActualPositions(pointPrimitiveCollection, pointPrimitivesToUpdate, pointPrimitiveCollection._pointPrimitivesToUpdateIndex, frameState, modelMatrix, false);
}
}
function updateBoundingVolume(collection, frameState, boundingVolume) {
var pixelSize = frameState.camera.getPixelSize(boundingVolume, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
var size = pixelSize * collection._maxPixelSize;
boundingVolume.radius += size;
}
var scratchWriterArray = [];
/**
* @private
*/
PointPrimitiveCollection.prototype.update = function(frameState) {
removePointPrimitives(this);
this._maxTotalPointSize = ContextLimits.maximumAliasedPointSize;
updateMode(this, frameState);
var pointPrimitives = this._pointPrimitives;
var pointPrimitivesLength = pointPrimitives.length;
var pointPrimitivesToUpdate = this._pointPrimitivesToUpdate;
var pointPrimitivesToUpdateLength = this._pointPrimitivesToUpdateIndex;
var properties = this._propertiesChanged;
var createVertexArray = this._createVertexArray;
var vafWriters;
var context = frameState.context;
var pass = frameState.passes;
var picking = pass.pick;
// PERFORMANCE_IDEA: Round robin multiple buffers.
if (createVertexArray || (!picking && this.computeNewBuffersUsage())) {
this._createVertexArray = false;
for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
properties[k] = 0;
}
this._vaf = this._vaf && this._vaf.destroy();
if (pointPrimitivesLength > 0) {
// PERFORMANCE_IDEA: Instead of creating a new one, resize like std::vector.
this._vaf = createVAF(context, pointPrimitivesLength, this._buffersUsage);
vafWriters = this._vaf.writers;
// Rewrite entire buffer if pointPrimitives were added or removed.
for (var i = 0; i < pointPrimitivesLength; ++i) {
var pointPrimitive = this._pointPrimitives[i];
pointPrimitive._dirty = false; // In case it needed an update.
writePointPrimitive(this, context, vafWriters, pointPrimitive);
}
this._vaf.commit();
}
this._pointPrimitivesToUpdateIndex = 0;
} else {
// PointPrimitives were modified, but none were added or removed.
if (pointPrimitivesToUpdateLength > 0) {
var writers = scratchWriterArray;
writers.length = 0;
if (properties[POSITION_INDEX] || properties[OUTLINE_WIDTH_INDEX] || properties[PIXEL_SIZE_INDEX]) {
writers.push(writePositionSizeAndOutline);
}
if (properties[COLOR_INDEX] || properties[OUTLINE_COLOR_INDEX]) {
writers.push(writeCompressedAttrib0);
}
if (properties[SHOW_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) {
writers.push(writeCompressedAttrib1);
}
if (properties[SCALE_BY_DISTANCE_INDEX]) {
writers.push(writeScaleByDistance);
}
if (properties[DISTANCE_DISPLAY_CONDITION_INDEX]) {
writers.push(writeDistanceDisplayCondition);
}
var numWriters = writers.length;
vafWriters = this._vaf.writers;
if ((pointPrimitivesToUpdateLength / pointPrimitivesLength) > 0.1) {
// If more than 10% of pointPrimitive change, rewrite the entire buffer.
// PERFORMANCE_IDEA: I totally made up 10% :).
for (var m = 0; m < pointPrimitivesToUpdateLength; ++m) {
var b = pointPrimitivesToUpdate[m];
b._dirty = false;
for ( var n = 0; n < numWriters; ++n) {
writers[n](this, context, vafWriters, b);
}
}
this._vaf.commit();
} else {
for (var h = 0; h < pointPrimitivesToUpdateLength; ++h) {
var bb = pointPrimitivesToUpdate[h];
bb._dirty = false;
for ( var o = 0; o < numWriters; ++o) {
writers[o](this, context, vafWriters, bb);
}
this._vaf.subCommit(bb._index, 1);
}
this._vaf.endSubCommits();
}
this._pointPrimitivesToUpdateIndex = 0;
}
}
// If the number of total pointPrimitives ever shrinks considerably
// Truncate pointPrimitivesToUpdate so that we free memory that we're
// not going to be using.
if (pointPrimitivesToUpdateLength > pointPrimitivesLength * 1.5) {
pointPrimitivesToUpdate.length = pointPrimitivesLength;
}
if (!defined(this._vaf) || !defined(this._vaf.va)) {
return;
}
if (this._boundingVolumeDirty) {
this._boundingVolumeDirty = false;
BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC);
}
var boundingVolume;
var modelMatrix = Matrix4.IDENTITY;
if (frameState.mode === SceneMode.SCENE3D) {
modelMatrix = this.modelMatrix;
boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume);
} else {
boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume);
}
updateBoundingVolume(this, frameState, boundingVolume);
var va;
var vaLength;
var command;
var j;
var vs;
var fs;
var commandList = frameState.commandList;
if (pass.render) {
var colorList = this._colorCommands;
if (!defined(this._rs)) {
this._rs = RenderState.fromCache({
depthTest : {
enabled : true,
func : WebGLConstants.LEQUAL
},
blending : BlendingState.ALPHA_BLEND
});
}
if (!defined(this._sp) ||
(this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) ||
(this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) ||
(this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition)) {
vs = new ShaderSource({
sources : [PointPrimitiveCollectionVS]
});
if (this._shaderScaleByDistance) {
vs.defines.push('EYE_DISTANCE_SCALING');
}
if (this._shaderTranslucencyByDistance) {
vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
}
if (this._shaderDistanceDisplayCondition) {
vs.defines.push('DISTANCE_DISPLAY_CONDITION');
}
this._sp = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._sp,
vertexShaderSource : vs,
fragmentShaderSource : PointPrimitiveCollectionFS,
attributeLocations : attributeLocations
});
this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance;
this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition;
}
va = this._vaf.va;
vaLength = va.length;
colorList.length = vaLength;
for (j = 0; j < vaLength; ++j) {
command = colorList[j];
if (!defined(command)) {
command = colorList[j] = new DrawCommand({
primitiveType : PrimitiveType.POINTS,
pass : Pass.OPAQUE,
owner : this
});
}
command.boundingVolume = boundingVolume;
command.modelMatrix = modelMatrix;
command.shaderProgram = this._sp;
command.uniformMap = this._uniforms;
command.vertexArray = va[j].va;
command.renderState = this._rs;
command.debugShowBoundingVolume = this.debugShowBoundingVolume;
commandList.push(command);
}
}
if (picking) {
var pickList = this._pickCommands;
if (!defined(this._spPick) ||
(this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) ||
(this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) ||
(this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick)) {
vs = new ShaderSource({
defines : ['RENDER_FOR_PICK'],
sources : [PointPrimitiveCollectionVS]
});
if (this._shaderScaleByDistance) {
vs.defines.push('EYE_DISTANCE_SCALING');
}
if (this._shaderTranslucencyByDistance) {
vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
}
if (this._shaderDistanceDisplayCondition) {
vs.defines.push('DISTANCE_DISPLAY_CONDITION');
}
fs = new ShaderSource({
defines : ['RENDER_FOR_PICK'],
sources : [PointPrimitiveCollectionFS]
});
this._spPick = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._spPick,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance;
this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance;
this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition;
}
va = this._vaf.va;
vaLength = va.length;
pickList.length = vaLength;
for (j = 0; j < vaLength; ++j) {
command = pickList[j];
if (!defined(command)) {
command = pickList[j] = new DrawCommand({
primitiveType : PrimitiveType.POINTS,
pass : Pass.OPAQUE,
owner : this
});
}
command.boundingVolume = boundingVolume;
command.modelMatrix = modelMatrix;
command.shaderProgram = this._spPick;
command.uniformMap = this._uniforms;
command.vertexArray = va[j].va;
command.renderState = this._rs;
commandList.push(command);
}
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see PointPrimitiveCollection#destroy
*/
PointPrimitiveCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* pointPrimitives = pointPrimitives && pointPrimitives.destroy();
*
* @see PointPrimitiveCollection#isDestroyed
*/
PointPrimitiveCollection.prototype.destroy = function() {
this._sp = this._sp && this._sp.destroy();
this._spPick = this._spPick && this._spPick.destroy();
this._vaf = this._vaf && this._vaf.destroy();
destroyPointPrimitives(this._pointPrimitives);
return destroyObject(this);
};
return PointPrimitiveCollection;
});
/*global define*/
define('ThirdParty/kdbush',[], function() {
'use strict';
function kdbush(points, getX, getY, nodeSize, ArrayType) {
return new KDBush(points, getX, getY, nodeSize, ArrayType);
}
function KDBush(points, getX, getY, nodeSize, ArrayType) {
getX = getX || defaultGetX;
getY = getY || defaultGetY;
ArrayType = ArrayType || Array;
this.nodeSize = nodeSize || 64;
this.points = points;
this.ids = new ArrayType(points.length);
this.coords = new ArrayType(points.length * 2);
for (var i = 0; i < points.length; i++) {
this.ids[i] = i;
this.coords[2 * i] = getX(points[i]);
this.coords[2 * i + 1] = getY(points[i]);
}
sort(this.ids, this.coords, this.nodeSize, 0, this.ids.length - 1, 0);
}
KDBush.prototype = {
range: function (minX, minY, maxX, maxY) {
return range(this.ids, this.coords, minX, minY, maxX, maxY, this.nodeSize);
},
within: function (x, y, r) {
return within(this.ids, this.coords, x, y, r, this.nodeSize);
}
};
function defaultGetX(p) { return p[0]; }
function defaultGetY(p) { return p[1]; }
function range(ids, coords, minX, minY, maxX, maxY, nodeSize) {
var stack = [0, ids.length - 1, 0];
var result = [];
var x, y;
while (stack.length) {
var axis = stack.pop();
var right = stack.pop();
var left = stack.pop();
if (right - left <= nodeSize) {
for (var i = left; i <= right; i++) {
x = coords[2 * i];
y = coords[2 * i + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]);
}
continue;
}
var m = Math.floor((left + right) / 2);
x = coords[2 * m];
y = coords[2 * m + 1];
if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]);
var nextAxis = (axis + 1) % 2;
if (axis === 0 ? minX <= x : minY <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(nextAxis);
}
if (axis === 0 ? maxX >= x : maxY >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(nextAxis);
}
}
return result;
}
function sort(ids, coords, nodeSize, left, right, depth) {
if (right - left <= nodeSize) return;
var m = Math.floor((left + right) / 2);
select(ids, coords, m, left, right, depth % 2);
sort(ids, coords, nodeSize, left, m - 1, depth + 1);
sort(ids, coords, nodeSize, m + 1, right, depth + 1);
}
function select(ids, coords, k, left, right, inc) {
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
select(ids, coords, k, newLeft, newRight, inc);
}
var t = coords[2 * k + inc];
var i = left;
var j = right;
swapItem(ids, coords, left, k);
if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right);
while (i < j) {
swapItem(ids, coords, i, j);
i++;
j--;
while (coords[2 * i + inc] < t) i++;
while (coords[2 * j + inc] > t) j--;
}
if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j);
else {
j++;
swapItem(ids, coords, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
function swapItem(ids, coords, i, j) {
swap(ids, i, j);
swap(coords, 2 * i, 2 * j);
swap(coords, 2 * i + 1, 2 * j + 1);
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function within(ids, coords, qx, qy, r, nodeSize) {
var stack = [0, ids.length - 1, 0];
var result = [];
var r2 = r * r;
while (stack.length) {
var axis = stack.pop();
var right = stack.pop();
var left = stack.pop();
if (right - left <= nodeSize) {
for (var i = left; i <= right; i++) {
if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]);
}
continue;
}
var m = Math.floor((left + right) / 2);
var x = coords[2 * m];
var y = coords[2 * m + 1];
if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]);
var nextAxis = (axis + 1) % 2;
if (axis === 0 ? qx - r <= x : qy - r <= y) {
stack.push(left);
stack.push(m - 1);
stack.push(nextAxis);
}
if (axis === 0 ? qx + r >= x : qy + r >= y) {
stack.push(m + 1);
stack.push(right);
stack.push(nextAxis);
}
}
return result;
}
function sqDist(ax, ay, bx, by) {
var dx = ax - bx;
var dy = ay - by;
return dx * dx + dy * dy;
}
return kdbush;
});
/*global define*/
define('DataSources/EntityCluster',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/EllipsoidalOccluder',
'../Core/Event',
'../Core/Matrix4',
'../Scene/Billboard',
'../Scene/BillboardCollection',
'../Scene/Label',
'../Scene/LabelCollection',
'../Scene/PointPrimitive',
'../Scene/PointPrimitiveCollection',
'../ThirdParty/kdbush'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
defaultValue,
defined,
defineProperties,
EllipsoidalOccluder,
Event,
Matrix4,
Billboard,
BillboardCollection,
Label,
LabelCollection,
PointPrimitive,
PointPrimitiveCollection,
kdbush) {
'use strict';
/**
* Defines how screen space objects (billboards, points, labels) are clustered.
*
* @param {Object} [options] An object with the following properties:
* @param {Boolean} [options.enabled=false] Whether or not to enable clustering.
* @param {Number} [options.pixelRange=80] The pixel range to extend the screen space bounding box.
* @param {Number} [options.minimumClusterSize=2] The minimum number of screen space objects that can be clustered.
* @param {Boolean} [options.clusterBillboards=true] Whether or not to cluster the billboards of an entity.
* @param {Boolean} [options.clusterLabels=true] Whether or not to cluster the labels of an entity.
* @param {Boolean} [options.clusterPoints=true] Whether or not to cluster the points of an entity.
*
* @alias EntityCluster
* @constructor
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Clustering.html|Cesium Sandcastle Clustering Demo}
*/
function EntityCluster(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._enabled = defaultValue(options.enabled, false);
this._pixelRange = defaultValue(options.pixelRange, 80);
this._minimumClusterSize = defaultValue(options.minimumClusterSize, 2);
this._clusterBillboards = defaultValue(options.clusterBillboards, true);
this._clusterLabels = defaultValue(options.clusterLabels, true);
this._clusterPoints = defaultValue(options.clusterPoints, true);
this._labelCollection = undefined;
this._billboardCollection = undefined;
this._pointCollection = undefined;
this._clusterBillboardCollection = undefined;
this._clusterLabelCollection = undefined;
this._clusterPointCollection = undefined;
this._collectionIndicesByEntity = {};
this._unusedLabelIndices = [];
this._unusedBillboardIndices = [];
this._unusedPointIndices = [];
this._previousClusters = [];
this._previousHeight = undefined;
this._enabledDirty = false;
this._clusterDirty = false;
this._cluster = undefined;
this._removeEventListener = undefined;
this._clusterEvent = new Event();
}
function getX(point) {
return point.coord.x;
}
function getY(point) {
return point.coord.y;
}
function expandBoundingBox(bbox, pixelRange) {
bbox.x -= pixelRange;
bbox.y -= pixelRange;
bbox.width += pixelRange * 2.0;
bbox.height += pixelRange * 2.0;
}
var labelBoundingBoxScratch = new BoundingRectangle();
function getBoundingBox(item, coord, pixelRange, entityCluster, result) {
if (defined(item._labelCollection) && entityCluster._clusterLabels) {
result = Label.getScreenSpaceBoundingBox(item, coord, result);
} else if (defined(item._billboardCollection) && entityCluster._clusterBillboards) {
result = Billboard.getScreenSpaceBoundingBox(item, coord, result);
} else if (defined(item._pointPrimitiveCollection) && entityCluster._clusterPoints) {
result = PointPrimitive.getScreenSpaceBoundingBox(item, coord, result);
}
expandBoundingBox(result, pixelRange);
if (entityCluster._clusterLabels && !defined(item._labelCollection) && defined(item.id) && hasLabelIndex(entityCluster, item.id) && defined(item.id._label)) {
var labelIndex = entityCluster._collectionIndicesByEntity[item.id];
var label = entityCluster._labelCollection.get(labelIndex);
var labelBBox = Label.getScreenSpaceBoundingBox(label, coord, labelBoundingBoxScratch);
expandBoundingBox(labelBBox, pixelRange);
result = BoundingRectangle.union(result, labelBBox, result);
}
return result;
}
function addNonClusteredItem(item, entityCluster) {
item.clusterShow = true;
if (!defined(item._labelCollection) && defined(item.id) && hasLabelIndex(entityCluster, item.id) && defined(item.id._label)) {
var labelIndex = entityCluster._collectionIndicesByEntity[item.id];
var label = entityCluster._labelCollection.get(labelIndex);
label.clusterShow = true;
}
}
function addCluster(position, numPoints, ids, entityCluster) {
var cluster = {
billboard : entityCluster._clusterBillboardCollection.add(),
label : entityCluster._clusterLabelCollection.add(),
point : entityCluster._clusterPointCollection.add()
};
cluster.billboard.show = false;
cluster.point.show = false;
cluster.label.show = true;
cluster.label.text = numPoints.toLocaleString();
cluster.billboard.position = cluster.label.position = cluster.point.position = position;
entityCluster._clusterEvent.raiseEvent(ids, cluster);
}
function hasLabelIndex(entityCluster, entityId) {
return defined(entityCluster) && defined(entityCluster._collectionIndicesByEntity[entityId]) && defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex);
}
function getScreenSpacePositions(collection, points, scene, occluder, entityCluster) {
if (!defined(collection)) {
return;
}
var length = collection.length;
for (var i = 0; i < length; ++i) {
var item = collection.get(i);
item.clusterShow = false;
if (!item.show || !occluder.isPointVisible(item.position)) {
continue;
}
var canClusterLabels = entityCluster._clusterLabels && defined(item._labelCollection);
var canClusterBillboards = entityCluster._clusterBillboards && defined(item.id._billboard);
var canClusterPoints = entityCluster._clusterPoints && defined(item.id._point);
if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
continue;
}
var coord = item.computeScreenSpacePosition(scene);
if (!defined(coord)) {
continue;
}
points.push({
index : i,
collection : collection,
clustered : false,
coord : coord
});
}
}
var pointBoundinRectangleScratch = new BoundingRectangle();
var totalBoundingRectangleScratch = new BoundingRectangle();
var neighborBoundingRectangleScratch = new BoundingRectangle();
function createDeclutterCallback(entityCluster) {
return function(amount) {
if ((defined(amount) && amount < 0.05) || !entityCluster.enabled) {
return;
}
var scene = entityCluster._scene;
var labelCollection = entityCluster._labelCollection;
var billboardCollection = entityCluster._billboardCollection;
var pointCollection = entityCluster._pointCollection;
if ((!defined(labelCollection) && !defined(billboardCollection) && !defined(pointCollection)) ||
(!entityCluster._clusterBillboards && !entityCluster._clusterLabels && !entityCluster._clusterPoints)) {
return;
}
var clusteredLabelCollection = entityCluster._clusterLabelCollection;
var clusteredBillboardCollection = entityCluster._clusterBillboardCollection;
var clusteredPointCollection = entityCluster._clusterPointCollection;
if (defined(clusteredLabelCollection)) {
clusteredLabelCollection.removeAll();
} else {
clusteredLabelCollection = entityCluster._clusterLabelCollection = new LabelCollection({
scene : scene
});
}
if (defined(clusteredBillboardCollection)) {
clusteredBillboardCollection.removeAll();
} else {
clusteredBillboardCollection = entityCluster._clusterBillboardCollection = new BillboardCollection({
scene : scene
});
}
if (defined(clusteredPointCollection)) {
clusteredPointCollection.removeAll();
} else {
clusteredPointCollection = entityCluster._clusterPointCollection = new PointPrimitiveCollection();
}
var pixelRange = entityCluster._pixelRange;
var minimumClusterSize = entityCluster._minimumClusterSize;
var clusters = entityCluster._previousClusters;
var newClusters = [];
var previousHeight = entityCluster._previousHeight;
var currentHeight = scene.camera.positionCartographic.height;
var ellipsoid = scene.mapProjection.ellipsoid;
var cameraPosition = scene.camera.positionWC;
var occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition);
var points = [];
if (entityCluster._clusterLabels) {
getScreenSpacePositions(labelCollection, points, scene, occluder, entityCluster);
}
if (entityCluster._clusterBillboards) {
getScreenSpacePositions(billboardCollection, points, scene, occluder, entityCluster);
}
if (entityCluster._clusterPoints) {
getScreenSpacePositions(pointCollection, points, scene, occluder, entityCluster);
}
var i;
var j;
var length;
var bbox;
var neighbors;
var neighborLength;
var neighborIndex;
var neighborPoint;
var ids;
var numPoints;
var collection;
var collectionIndex;
var index = kdbush(points, getX, getY, 64, Int32Array);
if (currentHeight < previousHeight) {
length = clusters.length;
for (i = 0; i < length; ++i) {
var cluster = clusters[i];
if (!occluder.isPointVisible(cluster.position)) {
continue;
}
var coord = Billboard._computeScreenSpacePosition(Matrix4.IDENTITY, cluster.position, Cartesian3.ZERO, Cartesian2.ZERO, scene);
if (!defined(coord)) {
continue;
}
var factor = 1.0 - currentHeight / previousHeight;
var width = cluster.width = cluster.width * factor;
var height = cluster.height = cluster.height * factor;
width = Math.max(width, cluster.minimumWidth);
height = Math.max(height, cluster.minimumHeight);
var minX = coord.x - width * 0.5;
var minY = coord.y - height * 0.5;
var maxX = coord.x + width;
var maxY = coord.y + height;
neighbors = index.range(minX, minY, maxX, maxY);
neighborLength = neighbors.length;
numPoints = 0;
ids = [];
for (j = 0; j < neighborLength; ++j) {
neighborIndex = neighbors[j];
neighborPoint = points[neighborIndex];
if (!neighborPoint.clustered) {
++numPoints;
collection = neighborPoint.collection;
collectionIndex = neighborPoint.index;
ids.push(collection.get(collectionIndex).id);
}
}
if (numPoints >= minimumClusterSize) {
addCluster(cluster.position, numPoints, ids, entityCluster);
newClusters.push(cluster);
for (j = 0; j < neighborLength; ++j) {
points[neighbors[j]].clustered = true;
}
}
}
}
length = points.length;
for (i = 0; i < length; ++i) {
var point = points[i];
if (point.clustered) {
continue;
}
point.clustered = true;
collection = point.collection;
collectionIndex = point.index;
var item = collection.get(collectionIndex);
bbox = getBoundingBox(item, point.coord, pixelRange, entityCluster, pointBoundinRectangleScratch);
var totalBBox = BoundingRectangle.clone(bbox, totalBoundingRectangleScratch);
neighbors = index.range(bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height);
neighborLength = neighbors.length;
var clusterPosition = Cartesian3.clone(item.position);
numPoints = 1;
ids = [item.id];
for (j = 0; j < neighborLength; ++j) {
neighborIndex = neighbors[j];
neighborPoint = points[neighborIndex];
if (!neighborPoint.clustered) {
var neighborItem = neighborPoint.collection.get(neighborPoint.index);
var neighborBBox = getBoundingBox(neighborItem, neighborPoint.coord, pixelRange, entityCluster, neighborBoundingRectangleScratch);
Cartesian3.add(neighborItem.position, clusterPosition, clusterPosition);
BoundingRectangle.union(totalBBox, neighborBBox, totalBBox);
++numPoints;
ids.push(neighborItem.id);
}
}
if (numPoints >= minimumClusterSize) {
var position = Cartesian3.multiplyByScalar(clusterPosition, 1.0 / numPoints, clusterPosition);
addCluster(position, numPoints, ids, entityCluster);
newClusters.push({
position : position,
width : totalBBox.width,
height : totalBBox.height,
minimumWidth : bbox.width,
minimumHeight : bbox.height
});
for (j = 0; j < neighborLength; ++j) {
points[neighbors[j]].clustered = true;
}
} else {
addNonClusteredItem(item, entityCluster);
}
}
if (clusteredLabelCollection.length === 0) {
clusteredLabelCollection.destroy();
entityCluster._clusterLabelCollection = undefined;
}
if (clusteredBillboardCollection.length === 0) {
clusteredBillboardCollection.destroy();
entityCluster._clusterBillboardCollection = undefined;
}
if (clusteredPointCollection.length === 0) {
clusteredPointCollection.destroy();
entityCluster._clusterPointCollection = undefined;
}
entityCluster._previousClusters = newClusters;
entityCluster._previousHeight = currentHeight;
};
}
EntityCluster.prototype._initialize = function(scene) {
this._scene = scene;
var cluster = createDeclutterCallback(this);
this._cluster = cluster;
this._removeEventListener = scene.camera.changed.addEventListener(cluster);
};
defineProperties(EntityCluster.prototype, {
/**
* Gets or sets whether clustering is enabled.
* @memberof EntityCluster.prototype
* @type {Boolean}
*/
enabled : {
get : function() {
return this._enabled;
},
set : function(value) {
this._enabledDirty = value !== this._enabled;
this._enabled = value;
}
},
/**
* Gets or sets the pixel range to extend the screen space bounding box.
* @memberof EntityCluster.prototype
* @type {Number}
*/
pixelRange : {
get : function() {
return this._pixelRange;
},
set : function(value) {
this._clusterDirty = this._clusterDirty || value !== this._pixelRange;
this._pixelRange = value;
}
},
/**
* Gets or sets the minimum number of screen space objects that can be clustered.
* @memberof EntityCluster.prototype
* @type {Number}
*/
minimumClusterSize : {
get : function() {
return this._minimumClusterSize;
},
set : function(value) {
this._clusterDirty = this._clusterDirty || value !== this._minimumClusterSize;
this._minimumClusterSize = value;
}
},
/**
* Gets the event that will be raised when a new cluster will be displayed. The signature of the event listener is {@link EntityCluster~newClusterCallback}.
* @memberof EntityCluster.prototype
* @type {Event}
*/
clusterEvent : {
get : function() {
return this._clusterEvent;
}
},
/**
* Gets or sets whether clustering billboard entities is enabled.
* @memberof EntityCluster.prototype
* @type {Boolean}
*/
clusterBillboards : {
get : function() {
return this._clusterBillboards;
},
set : function(value) {
this._clusterDirty = this._clusterDirty || value !== this._clusterBillboards;
this._clusterBillboards = value;
}
},
/**
* Gets or sets whether clustering labels entities is enabled.
* @memberof EntityCluster.prototype
* @type {Boolean}
*/
clusterLabels : {
get : function() {
return this._clusterLabels;
},
set : function(value) {
this._clusterDirty = this._clusterDirty || value !== this._clusterLabels;
this._clusterLabels = value;
}
},
/**
* Gets or sets whether clustering point entities is enabled.
* @memberof EntityCluster.prototype
* @type {Boolean}
*/
clusterPoints : {
get : function() {
return this._clusterPoints;
},
set : function(value) {
this._clusterDirty = this._clusterDirty || value !== this._clusterPoints;
this._clusterPoints = value;
}
}
});
function createGetEntity(collectionProperty, CollectionConstructor, unusedIndicesProperty, entityIndexProperty) {
return function(entity) {
var collection = this[collectionProperty];
if (!defined(this._collectionIndicesByEntity)) {
this._collectionIndicesByEntity = {};
}
var entityIndices = this._collectionIndicesByEntity[entity.id];
if (!defined(entityIndices)) {
entityIndices = this._collectionIndicesByEntity[entity.id] = {
billboardIndex: undefined,
labelIndex: undefined,
pointIndex: undefined
};
}
if (defined(collection) && defined(entityIndices[entityIndexProperty])) {
return collection.get(entityIndices[entityIndexProperty]);
}
if (!defined(collection)) {
collection = this[collectionProperty] = new CollectionConstructor({
scene : this._scene
});
}
var index;
var entityItem;
var unusedIndices = this[unusedIndicesProperty];
if (unusedIndices.length > 0) {
index = unusedIndices.pop();
entityItem = collection.get(index);
} else {
entityItem = collection.add();
index = collection.length - 1;
}
entityIndices[entityIndexProperty] = index;
this._clusterDirty = true;
return entityItem;
};
}
function removeEntityIndicesIfUnused(entityCluster, entityId) {
var indices = entityCluster._collectionIndicesByEntity[entityId];
if (!defined(indices.billboardIndex) && !defined(indices.labelIndex) && !defined(indices.pointIndex)) {
delete entityCluster._collectionIndicesByEntity[entityId];
}
}
/**
* Returns a new {@link Label}.
* @param {Entity} entity The entity that will use the returned {@link Label} for visualization.
* @returns {Label} The label that will be used to visualize an entity.
*
* @private
*/
EntityCluster.prototype.getLabel = createGetEntity('_labelCollection', LabelCollection, '_unusedLabelIndices', 'labelIndex');
/**
* Removes the {@link Label} associated with an entity so it can be reused by another entity.
* @param {Entity} entity The entity that will uses the returned {@link Label} for visualization.
*
* @private
*/
EntityCluster.prototype.removeLabel = function(entity) {
var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id];
if (!defined(this._labelCollection) || !defined(entityIndices) || !defined(entityIndices.labelIndex)) {
return;
}
var index = entityIndices.labelIndex;
entityIndices.labelIndex = undefined;
removeEntityIndicesIfUnused(this, entity.id);
var label = this._labelCollection.get(index);
label.show = false;
label.text = '';
label.id = undefined;
this._unusedLabelIndices.push(index);
this._clusterDirty = true;
};
/**
* Returns a new {@link Billboard}.
* @param {Entity} entity The entity that will use the returned {@link Billboard} for visualization.
* @returns {Billboard} The label that will be used to visualize an entity.
*
* @private
*/
EntityCluster.prototype.getBillboard = createGetEntity('_billboardCollection', BillboardCollection, '_unusedBillboardIndices', 'billboardIndex');
/**
* Removes the {@link Billboard} associated with an entity so it can be reused by another entity.
* @param {Entity} entity The entity that will uses the returned {@link Billboard} for visualization.
*
* @private
*/
EntityCluster.prototype.removeBillboard = function(entity) {
var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id];
if (!defined(this._billboardCollection) || !defined(entityIndices) || !defined(entityIndices.billboardIndex)) {
return;
}
var index = entityIndices.billboardIndex;
entityIndices.billboardIndex = undefined;
removeEntityIndicesIfUnused(this, entity.id);
var billboard = this._billboardCollection.get(index);
billboard.id = undefined;
billboard.show = false;
billboard.image = undefined;
this._unusedBillboardIndices.push(index);
this._clusterDirty = true;
};
/**
* Returns a new {@link Point}.
* @param {Entity} entity The entity that will use the returned {@link Point} for visualization.
* @returns {Point} The label that will be used to visualize an entity.
*
* @private
*/
EntityCluster.prototype.getPoint = createGetEntity('_pointCollection', PointPrimitiveCollection, '_unusedPointIndices', 'pointIndex');
/**
* Removes the {@link Point} associated with an entity so it can be reused by another entity.
* @param {Entity} entity The entity that will uses the returned {@link Point} for visualization.
*
* @private
*/
EntityCluster.prototype.removePoint = function(entity) {
var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id];
if (!defined(this._pointCollection) || !defined(entityIndices) || !defined(entityIndices.pointIndex)) {
return;
}
var index = entityIndices.pointIndex;
entityIndices.pointIndex = undefined;
removeEntityIndicesIfUnused(this, entity.id);
var point = this._pointCollection.get(index);
point.show = false;
point.id = undefined;
this._unusedPointIndices.push(index);
this._clusterDirty = true;
};
function disableCollectionClustering(collection) {
if (!defined(collection)) {
return;
}
var length = collection.length;
for (var i = 0; i < length; ++i) {
collection.get(i).clusterShow = true;
}
}
function updateEnable(entityCluster) {
if (entityCluster.enabled) {
return;
}
if (defined(entityCluster._clusterLabelCollection)) {
entityCluster._clusterLabelCollection.destroy();
}
if (defined(entityCluster._clusterBillboardCollection)) {
entityCluster._clusterBillboardCollection.destroy();
}
if (defined(entityCluster._clusterPointCollection)) {
entityCluster._clusterPointCollection.destroy();
}
entityCluster._clusterLabelCollection = undefined;
entityCluster._clusterBillboardCollection = undefined;
entityCluster._clusterPointCollection = undefined;
disableCollectionClustering(entityCluster._labelCollection);
disableCollectionClustering(entityCluster._billboardCollection);
disableCollectionClustering(entityCluster._pointCollection);
}
/**
* Gets the draw commands for the clustered billboards/points/labels if enabled, otherwise,
* queues the draw commands for billboards/points/labels created for entities.
* @private
*/
EntityCluster.prototype.update = function(frameState) {
// If clustering is enabled before the label collection is updated,
// the glyphs haven't been created so the screen space bounding boxes
// are incorrect.
if (defined(this._labelCollection) && this._labelCollection.length > 0 && this._labelCollection.get(0)._glyphs.length === 0) {
var commandList = frameState.commandList;
frameState.commandList = [];
this._labelCollection.update(frameState);
frameState.commandList = commandList;
}
if (this._enabledDirty) {
this._enabledDirty = false;
updateEnable(this);
this._clusterDirty = true;
}
if (this._clusterDirty) {
this._clusterDirty = false;
this._cluster();
}
if (defined(this._clusterLabelCollection)) {
this._clusterLabelCollection.update(frameState);
}
if (defined(this._clusterBillboardCollection)) {
this._clusterBillboardCollection.update(frameState);
}
if (defined(this._clusterPointCollection)) {
this._clusterPointCollection.update(frameState);
}
if (defined(this._labelCollection)) {
this._labelCollection.update(frameState);
}
if (defined(this._billboardCollection)) {
this._billboardCollection.update(frameState);
}
if (defined(this._pointCollection)) {
this._pointCollection.update(frameState);
}
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Unlike other objects that use WebGL resources, this object can be reused. For example, if a data source is removed
* from a data source collection and added to another.
*
*
* @returns {undefined}
*/
EntityCluster.prototype.destroy = function() {
this._labelCollection = this._labelCollection && this._labelCollection.destroy();
this._billboardCollection = this._billboardCollection && this._billboardCollection.destroy();
this._pointCollection = this._pointCollection && this._pointCollection.destroy();
this._clusterLabelCollection = this._clusterLabelCollection && this._clusterLabelCollection.destroy();
this._clusterBillboardCollection = this._clusterBillboardCollection && this._clusterBillboardCollection.destroy();
this._clusterPointCollection = this._clusterPointCollection && this._clusterPointCollection.destroy();
if (defined(this._removeEventListener)) {
this._removeEventListener();
this._removeEventListener = undefined;
}
this._labelCollection = undefined;
this._billboardCollection = undefined;
this._pointCollection = undefined;
this._clusterBillboardCollection = undefined;
this._clusterLabelCollection = undefined;
this._clusterPointCollection = undefined;
this._collectionIndicesByEntity = undefined;
this._unusedLabelIndices = [];
this._unusedBillboardIndices = [];
this._unusedPointIndices = [];
this._previousClusters = [];
this._previousHeight = undefined;
this._enabledDirty = false;
this._pixelRangeDirty = false;
this._minimumClusterSizeDirty = false;
return undefined;
};
/**
* A event listener function used to style clusters.
* @callback EntityCluster~newClusterCallback
*
* @param {Entity[]} clusteredEntities An array of the entities contained in the cluster.
* @param {Object} cluster An object containing billboard, label, and point properties. The values are the same as
* billboard, label and point entities, but must be the values of the ConstantProperty.
*
* @example
* // The default cluster values.
* dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) {
* cluster.label.show = true;
* cluster.label.text = entities.length.toLocaleString();
* });
*/
return EntityCluster;
});
/*global define*/
define('DataSources/CustomDataSource',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'./DataSource',
'./EntityCluster',
'./EntityCollection'
], function(
defined,
defineProperties,
DeveloperError,
Event,
DataSource,
EntityCluster,
EntityCollection) {
'use strict';
/**
* A {@link DataSource} implementation which can be used to manually manage a group of entities.
*
* @alias CustomDataSource
* @constructor
*
* @param {String} [name] A human-readable name for this instance.
*
* @example
* var dataSource = new Cesium.CustomDataSource('myData');
*
* var entity = dataSource.entities.add({
* position : Cesium.Cartesian3.fromDegrees(1, 2, 0),
* billboard : {
* image : 'image.png'
* }
* });
*
* viewer.dataSources.add(dataSource);
*/
function CustomDataSource(name) {
this._name = name;
this._clock = undefined;
this._changed = new Event();
this._error = new Event();
this._isLoading = false;
this._loading = new Event();
this._entityCollection = new EntityCollection(this);
this._entityCluster = new EntityCluster();
}
defineProperties(CustomDataSource.prototype, {
/**
* Gets or sets a human-readable name for this instance.
* @memberof CustomDataSource.prototype
* @type {String}
*/
name : {
get : function() {
return this._name;
},
set : function(value) {
if (this._name !== value) {
this._name = value;
this._changed.raiseEvent(this);
}
}
},
/**
* Gets or sets the clock for this instance.
* @memberof CustomDataSource.prototype
* @type {DataSourceClock}
*/
clock : {
get : function() {
return this._clock;
},
set : function(value) {
if (this._clock !== value) {
this._clock = value;
this._changed.raiseEvent(this);
}
}
},
/**
* Gets the collection of {@link Entity} instances.
* @memberof CustomDataSource.prototype
* @type {EntityCollection}
*/
entities : {
get : function() {
return this._entityCollection;
}
},
/**
* Gets or sets whether the data source is currently loading data.
* @memberof CustomDataSource.prototype
* @type {Boolean}
*/
isLoading : {
get : function() {
return this._isLoading;
},
set : function(value) {
DataSource.setLoading(this, value);
}
},
/**
* Gets an event that will be raised when the underlying data changes.
* @memberof CustomDataSource.prototype
* @type {Event}
*/
changedEvent : {
get : function() {
return this._changed;
}
},
/**
* Gets an event that will be raised if an error is encountered during processing.
* @memberof CustomDataSource.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._error;
}
},
/**
* Gets an event that will be raised when the data source either starts or stops loading.
* @memberof CustomDataSource.prototype
* @type {Event}
*/
loadingEvent : {
get : function() {
return this._loading;
}
},
/**
* Gets whether or not this data source should be displayed.
* @memberof CustomDataSource.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._entityCollection.show;
},
set : function(value) {
this._entityCollection.show = value;
}
},
/**
* Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
*
* @memberof CustomDataSource.prototype
* @type {EntityCluster}
*/
clustering : {
get : function() {
return this._entityCluster;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value must be defined.');
}
this._entityCluster = value;
}
}
});
return CustomDataSource;
});
/*global define*/
define('DataSources/CylinderGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/CylinderGeometry',
'../Core/CylinderOutlineGeometry',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
CylinderGeometry,
CylinderOutlineGeometry,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
ShowGeometryInstanceAttribute,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.length = undefined;
this.topRadius = undefined;
this.bottomRadius = undefined;
this.slices = undefined;
this.numberOfVerticalLines = undefined;
}
/**
* A {@link GeometryUpdater} for cylinders.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias CylinderGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function CylinderGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(CylinderGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'cylinder', entity.cylinder, undefined);
}
defineProperties(CylinderGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof CylinderGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof CylinderGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(CylinderGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if outline visibility varies with simulation time.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
value : true
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof CylinderGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
CylinderGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
CylinderGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
CylinderGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new CylinderGeometry(this._options),
modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
CylinderGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new CylinderOutlineGeometry(this._options),
modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
CylinderGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
CylinderGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
CylinderGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'cylinder')) {
return;
}
var cylinder = entity.cylinder;
if (!defined(cylinder)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = cylinder.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = cylinder.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var position = entity.position;
var length = cylinder.length;
var topRadius = cylinder.topRadius;
var bottomRadius = cylinder.bottomRadius;
var show = cylinder.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(position) || !defined(length) || !defined(topRadius) || !defined(bottomRadius))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(cylinder.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(cylinder.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(cylinder.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(cylinder.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(cylinder.distanceDisplayCondition, defaultDistanceDisplayCondition);
var slices = cylinder.slices;
var outlineWidth = cylinder.outlineWidth;
var numberOfVerticalLines = cylinder.numberOfVerticalLines;
this._fillEnabled = fillEnabled;
this._outlineEnabled = outlineEnabled;
if (!position.isConstant || //
!Property.isConstant(entity.orientation) || //
!length.isConstant || //
!topRadius.isConstant || //
!bottomRadius.isConstant || //
!Property.isConstant(slices) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(numberOfVerticalLines)) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.length = length.getValue(Iso8601.MINIMUM_VALUE);
options.topRadius = topRadius.getValue(Iso8601.MINIMUM_VALUE);
options.bottomRadius = bottomRadius.getValue(Iso8601.MINIMUM_VALUE);
options.slices = defined(slices) ? slices.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.numberOfVerticalLines = defined(numberOfVerticalLines) ? numberOfVerticalLines.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
CylinderGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, geometryUpdater) {
this._primitives = primitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._primitive = undefined;
this._outlinePrimitive = undefined;
var geometryUpdater = this._geometryUpdater;
var entity = geometryUpdater._entity;
var cylinder = entity.cylinder;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(cylinder.show, time, true)) {
return;
}
var options = this._options;
var modelMatrix = entity._getModelMatrix(time);
var length = Property.getValueOrUndefined(cylinder.length, time);
var topRadius = Property.getValueOrUndefined(cylinder.topRadius, time);
var bottomRadius = Property.getValueOrUndefined(cylinder.bottomRadius, time);
if (!defined(modelMatrix) || !defined(length) || !defined(topRadius) || !defined(bottomRadius)) {
return;
}
options.length = length;
options.topRadius = topRadius;
options.bottomRadius = bottomRadius;
options.slices = Property.getValueOrUndefined(cylinder.slices, time);
options.numberOfVerticalLines = Property.getValueOrUndefined(cylinder.numberOfVerticalLines, time);
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (Property.getValueOrDefault(cylinder.fill, time, true)) {
var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
this._material = material;
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : true
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new CylinderGeometry(options),
modelMatrix : modelMatrix,
attributes : {
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
if (Property.getValueOrDefault(cylinder.outline, time, false)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(cylinder.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(cylinder.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new CylinderOutlineGeometry(options),
modelMatrix : modelMatrix,
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return CylinderGeometryUpdater;
});
/*global define*/
define('Scene/ColorBlendMode',[
'../Core/freezeObject',
'../Core/Math'
], function(
freezeObject,
CesiumMath) {
'use strict';
/**
* Defines different modes for blending between a target color and a primitive's source color.
*
* HIGHLIGHT multiplies the source color by the target color
* REPLACE replaces the source color with the target color
* MIX blends the source color and target color together
*
* @exports ColorBlendMode
*
* @see Model.colorBlendMode
*/
var ColorBlendMode = {
HIGHLIGHT : 0,
REPLACE : 1,
MIX : 2
};
/**
* @private
*/
ColorBlendMode.getColorBlend = function(colorBlendMode, colorBlendAmount) {
if (colorBlendMode === ColorBlendMode.HIGHLIGHT) {
return 0.0;
} else if (colorBlendMode === ColorBlendMode.REPLACE) {
return 1.0;
} else if (colorBlendMode === ColorBlendMode.MIX) {
// The value 0.0 is reserved for highlight, so clamp to just above 0.0.
return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0);
}
};
return freezeObject(ColorBlendMode);
});
/*global define*/
define('DataSources/DataSourceClock',[
'../Core/Clock',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/JulianDate',
'./createRawPropertyDescriptor'
], function(
Clock,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
JulianDate,
createRawPropertyDescriptor) {
'use strict';
/**
* Represents desired clock settings for a particular {@link DataSource}. These settings may be applied
* to the {@link Clock} when the DataSource is loaded.
*
* @alias DataSourceClock
* @constructor
*/
function DataSourceClock() {
this._startTime = undefined;
this._stopTime = undefined;
this._currentTime = undefined;
this._clockRange = undefined;
this._clockStep = undefined;
this._multiplier = undefined;
this._definitionChanged = new Event();
}
defineProperties(DataSourceClock.prototype, {
/**
* Gets the event that is raised whenever a new property is assigned.
* @memberof DataSourceClock.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the desired start time of the clock.
* See {@link Clock#startTime}.
* @memberof DataSourceClock.prototype
* @type {JulianDate}
*/
startTime : createRawPropertyDescriptor('startTime'),
/**
* Gets or sets the desired stop time of the clock.
* See {@link Clock#stopTime}.
* @memberof DataSourceClock.prototype
* @type {JulianDate}
*/
stopTime : createRawPropertyDescriptor('stopTime'),
/**
* Gets or sets the desired current time when this data source is loaded.
* See {@link Clock#currentTime}.
* @memberof DataSourceClock.prototype
* @type {JulianDate}
*/
currentTime : createRawPropertyDescriptor('currentTime'),
/**
* Gets or sets the desired clock range setting.
* See {@link Clock#clockRange}.
* @memberof DataSourceClock.prototype
* @type {ClockRange}
*/
clockRange : createRawPropertyDescriptor('clockRange'),
/**
* Gets or sets the desired clock step setting.
* See {@link Clock#clockStep}.
* @memberof DataSourceClock.prototype
* @type {ClockStep}
*/
clockStep : createRawPropertyDescriptor('clockStep'),
/**
* Gets or sets the desired clock multiplier.
* See {@link Clock#multiplier}.
* @memberof DataSourceClock.prototype
* @type {Number}
*/
multiplier : createRawPropertyDescriptor('multiplier')
});
/**
* Duplicates a DataSourceClock instance.
*
* @param {DataSourceClock} [result] The object onto which to store the result.
* @returns {DataSourceClock} The modified result parameter or a new instance if one was not provided.
*/
DataSourceClock.prototype.clone = function(result) {
if (!defined(result)) {
result = new DataSourceClock();
}
result.startTime = this.startTime;
result.stopTime = this.stopTime;
result.currentTime = this.currentTime;
result.clockRange = this.clockRange;
result.clockStep = this.clockStep;
result.multiplier = this.multiplier;
return result;
};
/**
* Returns true if this DataSourceClock is equivalent to the other
*
* @param {DataSourceClock} other The other DataSourceClock to compare to.
* @returns {Boolean} true
if the DataSourceClocks are equal; otherwise, false
.
*/
DataSourceClock.prototype.equals = function(other) {
return this === other ||
defined(other) &&
JulianDate.equals(this.startTime, other.startTime) &&
JulianDate.equals(this.stopTime, other.stopTime) &&
JulianDate.equals(this.currentTime, other.currentTime) &&
this.clockRange === other.clockRange &&
this.clockStep === other.clockStep &&
this.multiplier === other.multiplier;
};
/**
* Assigns each unassigned property on this object to the value
* of the same property on the provided source object.
*
* @param {DataSourceClock} source The object to be merged into this object.
*/
DataSourceClock.prototype.merge = function(source) {
if (!defined(source)) {
throw new DeveloperError('source is required.');
}
this.startTime = defaultValue(this.startTime, source.startTime);
this.stopTime = defaultValue(this.stopTime, source.stopTime);
this.currentTime = defaultValue(this.currentTime, source.currentTime);
this.clockRange = defaultValue(this.clockRange, source.clockRange);
this.clockStep = defaultValue(this.clockStep, source.clockStep);
this.multiplier = defaultValue(this.multiplier, source.multiplier);
};
/**
* Gets the value of this clock instance as a {@link Clock} object.
*
* @returns {Clock} The modified result parameter or a new instance if one was not provided.
*/
DataSourceClock.prototype.getValue = function(result) {
if (!defined(result)) {
result = new Clock();
}
result.startTime = defaultValue(this.startTime, result.startTime);
result.stopTime = defaultValue(this.stopTime, result.stopTime);
result.currentTime = defaultValue(this.currentTime, result.currentTime);
result.clockRange = defaultValue(this.clockRange, result.clockRange);
result.multiplier = defaultValue(this.multiplier, result.multiplier);
result.clockStep = defaultValue(this.clockStep, result.clockStep);
return result;
};
return DataSourceClock;
});
/*global define*/
define('DataSources/GridMaterialProperty',[
'../Core/Cartesian2',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Cartesian2,
Color,
defaultValue,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
var defaultColor = Color.WHITE;
var defaultCellAlpha = 0.1;
var defaultLineCount = new Cartesian2(8, 8);
var defaultLineOffset = new Cartesian2(0, 0);
var defaultLineThickness = new Cartesian2(1, 1);
/**
* A {@link MaterialProperty} that maps to grid {@link Material} uniforms.
* @alias GridMaterialProperty
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.color=Color.WHITE] A Property specifying the grid {@link Color}.
* @param {Property} [options.cellAlpha=0.1] A numeric Property specifying cell alpha values.
* @param {Property} [options.lineCount=new Cartesian2(8, 8)] A {@link Cartesian2} Property specifying the number of grid lines along each axis.
* @param {Property} [options.lineThickness=new Cartesian2(1.0, 1.0)] A {@link Cartesian2} Property specifying the thickness of grid lines along each axis.
* @param {Property} [options.lineOffset=new Cartesian2(0.0, 0.0)] A {@link Cartesian2} Property specifying starting offset of grid lines along each axis.
*
* @constructor
*/
function GridMaterialProperty(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._color = undefined;
this._colorSubscription = undefined;
this._cellAlpha = undefined;
this._cellAlphaSubscription = undefined;
this._lineCount = undefined;
this._lineCountSubscription = undefined;
this._lineThickness = undefined;
this._lineThicknessSubscription = undefined;
this._lineOffset = undefined;
this._lineOffsetSubscription = undefined;
this.color = options.color;
this.cellAlpha = options.cellAlpha;
this.lineCount = options.lineCount;
this.lineThickness = options.lineThickness;
this.lineOffset = options.lineOffset;
}
defineProperties(GridMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof GridMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._color) &&
Property.isConstant(this._cellAlpha) &&
Property.isConstant(this._lineCount) &&
Property.isConstant(this._lineThickness) &&
Property.isConstant(this._lineOffset);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof GridMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the grid {@link Color}.
* @memberof GridMaterialProperty.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the numeric Property specifying cell alpha values.
* @memberof GridMaterialProperty.prototype
* @type {Property}
* @default 0.1
*/
cellAlpha : createPropertyDescriptor('cellAlpha'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the number of grid lines along each axis.
* @memberof GridMaterialProperty.prototype
* @type {Property}
* @default new Cartesian2(8.0, 8.0)
*/
lineCount : createPropertyDescriptor('lineCount'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the thickness of grid lines along each axis.
* @memberof GridMaterialProperty.prototype
* @type {Property}
* @default new Cartesian2(1.0, 1.0)
*/
lineThickness : createPropertyDescriptor('lineThickness'),
/**
* Gets or sets the {@link Cartesian2} Property specifying the starting offset of grid lines along each axis.
* @memberof GridMaterialProperty.prototype
* @type {Property}
* @default new Cartesian2(0.0, 0.0)
*/
lineOffset : createPropertyDescriptor('lineOffset')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
GridMaterialProperty.prototype.getType = function(time) {
return 'Grid';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
GridMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
result.cellAlpha = Property.getValueOrDefault(this._cellAlpha, time, defaultCellAlpha);
result.lineCount = Property.getValueOrClonedDefault(this._lineCount, time, defaultLineCount, result.lineCount);
result.lineThickness = Property.getValueOrClonedDefault(this._lineThickness, time, defaultLineThickness, result.lineThickness);
result.lineOffset = Property.getValueOrClonedDefault(this._lineOffset, time, defaultLineOffset, result.lineOffset);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
GridMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof GridMaterialProperty && //
Property.equals(this._color, other._color) && //
Property.equals(this._cellAlpha, other._cellAlpha) && //
Property.equals(this._lineCount, other._lineCount) && //
Property.equals(this._lineThickness, other._lineThickness) && //
Property.equals(this._lineOffset, other._lineOffset));
};
return GridMaterialProperty;
});
/*global define*/
define('DataSources/PolylineArrowMaterialProperty',[
'../Core/Color',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Color,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
/**
* A {@link MaterialProperty} that maps to PolylineArrow {@link Material} uniforms.
*
* @param {Property} [color=Color.WHITE] The {@link Color} Property to be used.
*
* @alias PolylineArrowMaterialProperty
* @constructor
*/
function PolylineArrowMaterialProperty(color) {
this._definitionChanged = new Event();
this._color = undefined;
this._colorSubscription = undefined;
this.color = color;
}
defineProperties(PolylineArrowMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof PolylineArrowMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._color);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof PolylineArrowMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the {@link Color} {@link Property}.
* @memberof PolylineArrowMaterialProperty.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
PolylineArrowMaterialProperty.prototype.getType = function(time) {
return 'PolylineArrow';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PolylineArrowMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.color = Property.getValueOrClonedDefault(this._color, time, Color.WHITE, result.color);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PolylineArrowMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof PolylineArrowMaterialProperty && //
Property.equals(this._color, other._color));
};
return PolylineArrowMaterialProperty;
});
/*global define*/
define('DataSources/PolylineGlowMaterialProperty',[
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Color,
defaultValue,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
var defaultColor = Color.WHITE;
var defaultGlowPower = 0.25;
/**
* A {@link MaterialProperty} that maps to polyline glow {@link Material} uniforms.
* @alias PolylineGlowMaterialProperty
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the line.
* @param {Property} [options.glowPower=0.25] A numeric Property specifying the strength of the glow, as a percentage of the total line width.
*/
function PolylineGlowMaterialProperty(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._color = undefined;
this._colorSubscription = undefined;
this._glowPower = undefined;
this._glowPowerSubscription = undefined;
this.color = options.color;
this.glowPower = options.glowPower;
}
defineProperties(PolylineGlowMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof PolylineGlowMaterialProperty.prototype
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._color) && Property.isConstant(this._glow);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof PolylineGlowMaterialProperty.prototype
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the {@link Color} of the line.
* @memberof PolylineGlowMaterialProperty.prototype
* @type {Property}
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the numeric Property specifying the strength of the glow, as a percentage of the total line width (less than 1.0).
* @memberof PolylineGlowMaterialProperty.prototype
* @type {Property}
*/
glowPower : createPropertyDescriptor('glowPower')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
PolylineGlowMaterialProperty.prototype.getType = function(time) {
return 'PolylineGlow';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PolylineGlowMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
result.glowPower = Property.getValueOrDefault(this._glowPower, time, defaultGlowPower, result.glowPower);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PolylineGlowMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof PolylineGlowMaterialProperty && //
Property.equals(this._color, other._color) &&
Property.equals(this._glowPower, other._glowPower));
};
return PolylineGlowMaterialProperty;
});
/*global define*/
define('DataSources/PolylineOutlineMaterialProperty',[
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property'
], function(
Color,
defaultValue,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property) {
'use strict';
var defaultColor = Color.WHITE;
var defaultOutlineColor = Color.BLACK;
var defaultOutlineWidth = 1.0;
/**
* A {@link MaterialProperty} that maps to polyline outline {@link Material} uniforms.
* @alias PolylineOutlineMaterialProperty
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the line.
* @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
* @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline, in pixels.
*/
function PolylineOutlineMaterialProperty(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._color = undefined;
this._colorSubscription = undefined;
this._outlineColor = undefined;
this._outlineColorSubscription = undefined;
this._outlineWidth = undefined;
this._outlineWidthSubscription = undefined;
this.color = options.color;
this.outlineColor = options.outlineColor;
this.outlineWidth = options.outlineWidth;
}
defineProperties(PolylineOutlineMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof PolylineOutlineMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._color) && Property.isConstant(this._outlineColor) && Property.isConstant(this._outlineWidth);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof PolylineOutlineMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the {@link Color} of the line.
* @memberof PolylineOutlineMaterialProperty.prototype
* @type {Property}
* @default Color.WHITE
*/
color : createPropertyDescriptor('color'),
/**
* Gets or sets the Property specifying the {@link Color} of the outline.
* @memberof PolylineOutlineMaterialProperty.prototype
* @type {Property}
* @default Color.BLACK
*/
outlineColor : createPropertyDescriptor('outlineColor'),
/**
* Gets or sets the numeric Property specifying the width of the outline.
* @memberof PolylineOutlineMaterialProperty.prototype
* @type {Property}
* @default 1.0
*/
outlineWidth : createPropertyDescriptor('outlineWidth')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
PolylineOutlineMaterialProperty.prototype.getType = function(time) {
return 'PolylineOutline';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PolylineOutlineMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
result.outlineColor = Property.getValueOrClonedDefault(this._outlineColor, time, defaultOutlineColor, result.outlineColor);
result.outlineWidth = Property.getValueOrDefault(this._outlineWidth, time, defaultOutlineWidth);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PolylineOutlineMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof PolylineOutlineMaterialProperty && //
Property.equals(this._color, other._color) && //
Property.equals(this._outlineColor, other._outlineColor) && //
Property.equals(this._outlineWidth, other._outlineWidth));
};
return PolylineOutlineMaterialProperty;
});
/*global define*/
define('DataSources/PositionPropertyArray',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/EventHelper',
'../Core/ReferenceFrame',
'./Property'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
EventHelper,
ReferenceFrame,
Property) {
'use strict';
/**
* A {@link PositionProperty} whose value is an array whose items are the computed value
* of other PositionProperty instances.
*
* @alias PositionPropertyArray
* @constructor
*
* @param {Property[]} [value] An array of Property instances.
* @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
*/
function PositionPropertyArray(value, referenceFrame) {
this._value = undefined;
this._definitionChanged = new Event();
this._eventHelper = new EventHelper();
this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
this.setValue(value);
}
defineProperties(PositionPropertyArray.prototype, {
/**
* Gets a value indicating if this property is constant. This property
* is considered constant if all property items in the array are constant.
* @memberof PositionPropertyArray.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
var value = this._value;
if (!defined(value)) {
return true;
}
var length = value.length;
for (var i = 0; i < length; i++) {
if (!Property.isConstant(value[i])) {
return false;
}
}
return true;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value or one of the properties in the array also changes.
* @memberof PositionPropertyArray.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the reference frame in which the position is defined.
* @memberof PositionPropertyArray.prototype
* @type {ReferenceFrame}
* @default ReferenceFrame.FIXED;
*/
referenceFrame : {
get : function() {
return this._referenceFrame;
}
}
});
/**
* Gets the value of the property.
*
* @param {JulianDate} [time] The time for which to retrieve the value. This parameter is unused since the value does not change with respect to time.
* @param {Cartesian3[]} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3[]} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PositionPropertyArray.prototype.getValue = function(time, result) {
return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
};
/**
* Gets the value of the property at the provided time and in the provided reference frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
PositionPropertyArray.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(referenceFrame)) {
throw new DeveloperError('referenceFrame is required.');
}
var value = this._value;
if (!defined(value)) {
return undefined;
}
var length = value.length;
if (!defined(result)) {
result = new Array(length);
}
var i = 0;
var x = 0;
while (i < length) {
var property = value[i];
var itemValue = property.getValueInReferenceFrame(time, referenceFrame, result[i]);
if (defined(itemValue)) {
result[x] = itemValue;
x++;
}
i++;
}
result.length = x;
return result;
};
/**
* Sets the value of the property.
*
* @param {Property[]} value An array of Property instances.
*/
PositionPropertyArray.prototype.setValue = function(value) {
var eventHelper = this._eventHelper;
eventHelper.removeAll();
if (defined(value)) {
this._value = value.slice();
var length = value.length;
for (var i = 0; i < length; i++) {
var property = value[i];
if (defined(property)) {
eventHelper.add(property.definitionChanged, PositionPropertyArray.prototype._raiseDefinitionChanged, this);
}
}
} else {
this._value = undefined;
}
this._definitionChanged.raiseEvent(this);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PositionPropertyArray.prototype.equals = function(other) {
return this === other || //
(other instanceof PositionPropertyArray && //
this._referenceFrame === other._referenceFrame && //
Property.arrayEquals(this._value, other._value));
};
PositionPropertyArray.prototype._raiseDefinitionChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return PositionPropertyArray;
});
/*global define*/
define('DataSources/PropertyArray',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/EventHelper',
'./Property'
], function(
defined,
defineProperties,
DeveloperError,
Event,
EventHelper,
Property) {
'use strict';
/**
* A {@link Property} whose value is an array whose items are the computed value
* of other property instances.
*
* @alias PropertyArray
* @constructor
*
* @param {Property[]} [value] An array of Property instances.
*/
function PropertyArray(value) {
this._value = undefined;
this._definitionChanged = new Event();
this._eventHelper = new EventHelper();
this.setValue(value);
}
defineProperties(PropertyArray.prototype, {
/**
* Gets a value indicating if this property is constant. This property
* is considered constant if all property items in the array are constant.
* @memberof PropertyArray.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
var value = this._value;
if (!defined(value)) {
return true;
}
var length = value.length;
for (var i = 0; i < length; i++) {
if (!Property.isConstant(value[i])) {
return false;
}
}
return true;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value or one of the properties in the array also changes.
* @memberof PropertyArray.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
}
});
/**
* Gets the value of the property.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object[]} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object[]} The modified result parameter, which is an array of values produced by evaluating each of the contained properties at the given time or a new instance if the result parameter was not supplied.
*/
PropertyArray.prototype.getValue = function(time, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var value = this._value;
if (!defined(value)) {
return undefined;
}
var length = value.length;
if (!defined(result)) {
result = new Array(length);
}
var i = 0;
var x = 0;
while (i < length) {
var property = this._value[i];
var itemValue = property.getValue(time, result[i]);
if (defined(itemValue)) {
result[x] = itemValue;
x++;
}
i++;
}
result.length = x;
return result;
};
/**
* Sets the value of the property.
*
* @param {Property[]} value An array of Property instances.
*/
PropertyArray.prototype.setValue = function(value) {
var eventHelper = this._eventHelper;
eventHelper.removeAll();
if (defined(value)) {
this._value = value.slice();
var length = value.length;
for (var i = 0; i < length; i++) {
var property = value[i];
if (defined(property)) {
eventHelper.add(property.definitionChanged, PropertyArray.prototype._raiseDefinitionChanged, this);
}
}
} else {
this._value = undefined;
}
this._definitionChanged.raiseEvent(this);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
PropertyArray.prototype.equals = function(other) {
return this === other || //
(other instanceof PropertyArray && //
Property.arrayEquals(this._value, other._value));
};
PropertyArray.prototype._raiseDefinitionChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return PropertyArray;
});
/*global define*/
define('DataSources/ReferenceProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/RuntimeError',
'./Property'
], function(
defined,
defineProperties,
DeveloperError,
Event,
RuntimeError,
Property) {
'use strict';
function resolveEntity(that) {
var entityIsResolved = true;
if (that._resolveEntity) {
var targetEntity = that._targetCollection.getById(that._targetId);
if (defined(targetEntity)) {
targetEntity.definitionChanged.addEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, that);
that._targetEntity = targetEntity;
that._resolveEntity = false;
} else {
//The property has become detached. It has a valid value but is not currently resolved to an entity in the collection
targetEntity = that._targetEntity;
entityIsResolved = false;
}
if (!defined(targetEntity)) {
throw new RuntimeError('target entity "' + that._targetId + '" could not be resolved.');
}
}
return entityIsResolved;
}
function resolve(that) {
var targetProperty = that._targetProperty;
if (that._resolveProperty) {
var entityIsResolved = resolveEntity(that);
var names = that._targetPropertyNames;
targetProperty = that._targetEntity;
var length = names.length;
for (var i = 0; i < length && defined(targetProperty); i++) {
targetProperty = targetProperty[names[i]];
}
if (defined(targetProperty)) {
that._targetProperty = targetProperty;
that._resolveProperty = !entityIsResolved;
} else if (!defined(that._targetProperty)) {
throw new RuntimeError('targetProperty "' + that._targetId + '.' + names.join('.') + '" could not be resolved.');
}
}
return targetProperty;
}
/**
* A {@link Property} which transparently links to another property on a provided object.
*
* @alias ReferenceProperty
* @constructor
*
* @param {EntityCollection} targetCollection The entity collection which will be used to resolve the reference.
* @param {String} targetId The id of the entity which is being referenced.
* @param {String[]} targetPropertyNames The names of the property on the target entity which we will use.
*
* @example
* var collection = new Cesium.EntityCollection();
*
* //Create a new entity and assign a billboard scale.
* var object1 = new Cesium.Entity({id:'object1'});
* object1.billboard = new Cesium.BillboardGraphics();
* object1.billboard.scale = new Cesium.ConstantProperty(2.0);
* collection.add(object1);
*
* //Create a second entity and reference the scale from the first one.
* var object2 = new Cesium.Entity({id:'object2'});
* object2.model = new Cesium.ModelGraphics();
* object2.model.scale = new Cesium.ReferenceProperty(collection, 'object1', ['billboard', 'scale']);
* collection.add(object2);
*
* //Create a third object, but use the fromString helper function.
* var object3 = new Cesium.Entity({id:'object3'});
* object3.billboard = new Cesium.BillboardGraphics();
* object3.billboard.scale = Cesium.ReferenceProperty.fromString(collection, 'object1#billboard.scale');
* collection.add(object3);
*
* //You can refer to an entity with a # or . in id and property names by escaping them.
* var object4 = new Cesium.Entity({id:'#object.4'});
* object4.billboard = new Cesium.BillboardGraphics();
* object4.billboard.scale = new Cesium.ConstantProperty(2.0);
* collection.add(object4);
*
* var object5 = new Cesium.Entity({id:'object5'});
* object5.billboard = new Cesium.BillboardGraphics();
* object5.billboard.scale = Cesium.ReferenceProperty.fromString(collection, '\\#object\\.4#billboard.scale');
* collection.add(object5);
*/
function ReferenceProperty(targetCollection, targetId, targetPropertyNames) {
if (!defined(targetCollection)) {
throw new DeveloperError('targetCollection is required.');
}
if (!defined(targetId) || targetId === '') {
throw new DeveloperError('targetId is required.');
}
if (!defined(targetPropertyNames) || targetPropertyNames.length === 0) {
throw new DeveloperError('targetPropertyNames is required.');
}
for (var i = 0; i < targetPropertyNames.length; i++) {
var item = targetPropertyNames[i];
if (!defined(item) || item === '') {
throw new DeveloperError('reference contains invalid properties.');
}
}
this._targetCollection = targetCollection;
this._targetId = targetId;
this._targetPropertyNames = targetPropertyNames;
this._targetProperty = undefined;
this._targetEntity = undefined;
this._definitionChanged = new Event();
this._resolveEntity = true;
this._resolveProperty = true;
targetCollection.collectionChanged.addEventListener(ReferenceProperty.prototype._onCollectionChanged, this);
}
defineProperties(ReferenceProperty.prototype, {
/**
* Gets a value indicating if this property is constant.
* @memberof ReferenceProperty.prototype
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(resolve(this));
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever the referenced property's definition is changed.
* @memberof ReferenceProperty.prototype
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the reference frame that the position is defined in.
* This property is only valid if the referenced property is a {@link PositionProperty}.
* @memberof ReferenceProperty.prototype
* @type {ReferenceFrame}
* @readonly
*/
referenceFrame : {
get : function() {
return resolve(this).referenceFrame;
}
},
/**
* Gets the id of the entity being referenced.
* @memberof ReferenceProperty.prototype
* @type {String}
* @readonly
*/
targetId : {
get : function() {
return this._targetId;
}
},
/**
* Gets the collection containing the entity being referenced.
* @memberof ReferenceProperty.prototype
* @type {EntityCollection}
* @readonly
*/
targetCollection : {
get : function() {
return this._targetCollection;
}
},
/**
* Gets the array of property names used to retrieve the referenced property.
* @memberof ReferenceProperty.prototype
* @type {String[]}
* @readonly
*/
targetPropertyNames : {
get : function() {
return this._targetPropertyNames;
}
},
/**
* Gets the resolved instance of the underlying referenced property.
* @memberof ReferenceProperty.prototype
* @type {Property}
* @readonly
*/
resolvedProperty : {
get : function() {
return resolve(this);
}
}
});
/**
* Creates a new instance given the entity collection that will
* be used to resolve it and a string indicating the target entity id and property.
* The format of the string is "objectId#foo.bar", where # separates the id from
* property path and . separates sub-properties. If the reference identifier or
* or any sub-properties contains a # . or \ they must be escaped.
*
* @param {EntityCollection} targetCollection
* @param {String} referenceString
* @returns {ReferenceProperty} A new instance of ReferenceProperty.
*
* @exception {DeveloperError} invalid referenceString.
*/
ReferenceProperty.fromString = function(targetCollection, referenceString) {
if (!defined(targetCollection)) {
throw new DeveloperError('targetCollection is required.');
}
if (!defined(referenceString)) {
throw new DeveloperError('referenceString is required.');
}
var identifier;
var values = [];
var inIdentifier = true;
var isEscaped = false;
var token = '';
for (var i = 0; i < referenceString.length; ++i) {
var c = referenceString.charAt(i);
if (isEscaped) {
token += c;
isEscaped = false;
} else if (c === '\\') {
isEscaped = true;
} else if (inIdentifier && c === '#') {
identifier = token;
inIdentifier = false;
token = '';
} else if (!inIdentifier && c === '.') {
values.push(token);
token = '';
} else {
token += c;
}
}
values.push(token);
return new ReferenceProperty(targetCollection, identifier, values);
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ReferenceProperty.prototype.getValue = function(time, result) {
return resolve(this).getValue(time, result);
};
/**
* Gets the value of the property at the provided time and in the provided reference frame.
* This method is only valid if the property being referenced is a {@link PositionProperty}.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
ReferenceProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
return resolve(this).getValueInReferenceFrame(time, referenceFrame, result);
};
/**
* Gets the {@link Material} type at the provided time.
* This method is only valid if the property being referenced is a {@link MaterialProperty}.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
ReferenceProperty.prototype.getType = function(time) {
return resolve(this).getType(time);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
ReferenceProperty.prototype.equals = function(other) {
if (this === other) {
return true;
}
var names = this._targetPropertyNames;
var otherNames = other._targetPropertyNames;
if (this._targetCollection !== other._targetCollection || //
this._targetId !== other._targetId || //
names.length !== otherNames.length) {
return false;
}
var length = this._targetPropertyNames.length;
for (var i = 0; i < length; i++) {
if (names[i] !== otherNames[i]) {
return false;
}
}
return true;
};
ReferenceProperty.prototype._onTargetEntityDefinitionChanged = function(targetEntity, name, value, oldValue) {
if (this._targetPropertyNames[0] === name) {
this._resolveProperty = true;
this._definitionChanged.raiseEvent(this);
}
};
ReferenceProperty.prototype._onCollectionChanged = function(collection, added, removed) {
var targetEntity = this._targetEntity;
if (defined(targetEntity)) {
if (removed.indexOf(targetEntity) !== -1) {
targetEntity.definitionChanged.removeEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, this);
this._resolveEntity = true;
this._resolveProperty = true;
} else if (this._resolveEntity) {
//If targetEntity is defined but resolveEntity is true, then the entity is detached
//and any change to the collection needs to incur an attempt to resolve in order to re-attach.
//without this if block, a reference that becomes re-attached will not signal definitionChanged
resolve(this);
if (!this._resolveEntity) {
this._definitionChanged.raiseEvent(this);
}
}
}
};
return ReferenceProperty;
});
/*global define*/
define('DataSources/Rotation',[
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Math'
], function(
defaultValue,
defined,
DeveloperError,
CesiumMath) {
'use strict';
/**
* Represents a {@link Packable} number that always interpolates values
* towards the shortest angle of rotation. This object is never used directly
* but is instead passed to the constructor of {@link SampledProperty}
* in order to represent a two-dimensional angle of rotation.
*
* @exports Rotation
*
*
* @example
* var time1 = Cesium.JulianDate.fromIso8601('2010-05-07T00:00:00');
* var time2 = Cesium.JulianDate.fromIso8601('2010-05-07T00:01:00');
* var time3 = Cesium.JulianDate.fromIso8601('2010-05-07T00:02:00');
*
* var property = new Cesium.SampledProperty(Cesium.Rotation);
* property.addSample(time1, 0);
* property.addSample(time3, Cesium.Math.toRadians(350));
*
* //Getting the value at time2 will equal 355 degrees instead
* //of 175 degrees (which is what you get if you construct
* //a SampledProperty(Number) instead. Note, the actual
* //return value is in radians, not degrees.
* property.getValue(time2);
*
* @see PackableForInterpolation
*/
var Rotation = {
/**
* The number of elements used to pack the object into an array.
* @type {Number}
*/
packedLength : 1,
/**
* Stores the provided instance into the provided array.
*
* @param {Rotation} value The value to pack.
* @param {Number[]} array The array to pack into.
* @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
*
* @returns {Number[]} The array that was packed into
*/
pack : function(value, array, startingIndex) {
if (!defined(value)) {
throw new DeveloperError('value is required');
}
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex] = value;
return array;
},
/**
* Retrieves an instance from a packed array.
*
* @param {Number[]} array The packed array.
* @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
* @param {Rotation} [result] The object into which to store the result.
* @returns {Rotation} The modified result parameter or a new Rotation instance if one was not provided.
*/
unpack : function(array, startingIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
startingIndex = defaultValue(startingIndex, 0);
return array[startingIndex];
},
/**
* Converts a packed array into a form suitable for interpolation.
*
* @param {Number[]} packedArray The packed array.
* @param {Number} [startingIndex=0] The index of the first element to be converted.
* @param {Number} [lastIndex=packedArray.length] The index of the last element to be converted.
* @param {Number[]} result The object into which to store the result.
*/
convertPackedArrayForInterpolation : function(packedArray, startingIndex, lastIndex, result) {
if (!defined(packedArray)) {
throw new DeveloperError('packedArray is required');
}
startingIndex = defaultValue(startingIndex, 0);
lastIndex = defaultValue(lastIndex, packedArray.length);
var previousValue;
for (var i = 0, len = lastIndex - startingIndex + 1; i < len; i++) {
var value = packedArray[startingIndex + i];
if (i === 0 || Math.abs(previousValue - value) < Math.PI) {
result[i] = value;
} else {
result[i] = value - CesiumMath.TWO_PI;
}
previousValue = value;
}
},
/**
* Retrieves an instance from a packed array converted with {@link Rotation.convertPackedArrayForInterpolation}.
*
* @param {Number[]} array The array previously packed for interpolation.
* @param {Number[]} sourceArray The original packed array.
* @param {Number} [startingIndex=0] The startingIndex used to convert the array.
* @param {Number} [lastIndex=packedArray.length] The lastIndex used to convert the array.
* @param {Rotation} [result] The object into which to store the result.
* @returns {Rotation} The modified result parameter or a new Rotation instance if one was not provided.
*/
unpackInterpolationResult : function(array, sourceArray, firstIndex, lastIndex, result) {
if (!defined(array)) {
throw new DeveloperError('array is required');
}
if (!defined(sourceArray)) {
throw new DeveloperError('sourceArray is required');
}
result = array[0];
if (result < 0) {
return result + CesiumMath.TWO_PI;
}
return result;
}
};
return Rotation;
});
/*global define*/
define('DataSources/SampledProperty',[
'../Core/binarySearch',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/ExtrapolationType',
'../Core/JulianDate',
'../Core/LinearApproximation'
], function(
binarySearch,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
ExtrapolationType,
JulianDate,
LinearApproximation) {
'use strict';
var PackableNumber = {
packedLength : 1,
pack : function(value, array, startingIndex) {
startingIndex = defaultValue(startingIndex, 0);
array[startingIndex] = value;
},
unpack : function(array, startingIndex, result) {
startingIndex = defaultValue(startingIndex, 0);
return array[startingIndex];
}
};
//We can't use splice for inserting new elements because function apply can't handle
//a huge number of arguments. See https://code.google.com/p/chromium/issues/detail?id=56588
function arrayInsert(array, startIndex, items) {
var i;
var arrayLength = array.length;
var itemsLength = items.length;
var newLength = arrayLength + itemsLength;
array.length = newLength;
if (arrayLength !== startIndex) {
var q = arrayLength - 1;
for (i = newLength - 1; i >= startIndex; i--) {
array[i] = array[q--];
}
}
for (i = 0; i < itemsLength; i++) {
array[startIndex++] = items[i];
}
}
function convertDate(date, epoch) {
if (date instanceof JulianDate) {
return date;
}
if (typeof date === 'string') {
return JulianDate.fromIso8601(date);
}
return JulianDate.addSeconds(epoch, date, new JulianDate());
}
var timesSpliceArgs = [];
var valuesSpliceArgs = [];
function mergeNewSamples(epoch, times, values, newData, packedLength) {
var newDataIndex = 0;
var i;
var prevItem;
var timesInsertionPoint;
var valuesInsertionPoint;
var currentTime;
var nextTime;
while (newDataIndex < newData.length) {
currentTime = convertDate(newData[newDataIndex], epoch);
timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);
var timesSpliceArgsCount = 0;
var valuesSpliceArgsCount = 0;
if (timesInsertionPoint < 0) {
//Doesn't exist, insert as many additional values as we can.
timesInsertionPoint = ~timesInsertionPoint;
valuesInsertionPoint = timesInsertionPoint * packedLength;
prevItem = undefined;
nextTime = times[timesInsertionPoint];
while (newDataIndex < newData.length) {
currentTime = convertDate(newData[newDataIndex], epoch);
if ((defined(prevItem) && JulianDate.compare(prevItem, currentTime) >= 0) || (defined(nextTime) && JulianDate.compare(currentTime, nextTime) >= 0)) {
break;
}
timesSpliceArgs[timesSpliceArgsCount++] = currentTime;
newDataIndex = newDataIndex + 1;
for (i = 0; i < packedLength; i++) {
valuesSpliceArgs[valuesSpliceArgsCount++] = newData[newDataIndex];
newDataIndex = newDataIndex + 1;
}
prevItem = currentTime;
}
if (timesSpliceArgsCount > 0) {
valuesSpliceArgs.length = valuesSpliceArgsCount;
arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs);
timesSpliceArgs.length = timesSpliceArgsCount;
arrayInsert(times, timesInsertionPoint, timesSpliceArgs);
}
} else {
//Found an exact match
for (i = 0; i < packedLength; i++) {
newDataIndex++;
values[(timesInsertionPoint * packedLength) + i] = newData[newDataIndex];
}
newDataIndex++;
}
}
}
/**
* A {@link Property} whose value is interpolated for a given time from the
* provided set of samples and specified interpolation algorithm and degree.
* @alias SampledProperty
* @constructor
*
* @param {Number|Packable} type The type of property.
* @param {Packable[]} [derivativeTypes] When supplied, indicates that samples will contain derivative information of the specified types.
*
*
* @example
* //Create a linearly interpolated Cartesian2
* var property = new Cesium.SampledProperty(Cesium.Cartesian2);
*
* //Populate it with data
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), new Cesium.Cartesian2(0, 0));
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-02T00:00:00.00Z`), new Cesium.Cartesian2(4, 7));
*
* //Retrieve an interpolated value
* var result = property.getValue(Cesium.JulianDate.fromIso8601(`2012-08-01T12:00:00.00Z`));
*
* @example
* //Create a simple numeric SampledProperty that uses third degree Hermite Polynomial Approximation
* var property = new Cesium.SampledProperty(Number);
* property.setInterpolationOptions({
* interpolationDegree : 3,
* interpolationAlgorithm : Cesium.HermitePolynomialApproximation
* });
*
* //Populate it with data
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), 1.0);
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:01:00.00Z`), 6.0);
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:02:00.00Z`), 12.0);
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:03:30.00Z`), 5.0);
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:06:30.00Z`), 2.0);
*
* //Samples can be added in any order.
* property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:00:30.00Z`), 6.2);
*
* //Retrieve an interpolated value
* var result = property.getValue(Cesium.JulianDate.fromIso8601(`2012-08-01T00:02:34.00Z`));
*
* @see SampledPositionProperty
*/
function SampledProperty(type, derivativeTypes) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var innerType = type;
if (innerType === Number) {
innerType = PackableNumber;
}
var packedLength = innerType.packedLength;
var packedInterpolationLength = defaultValue(innerType.packedInterpolationLength, packedLength);
var inputOrder = 0;
var innerDerivativeTypes;
if (defined(derivativeTypes)) {
var length = derivativeTypes.length;
innerDerivativeTypes = new Array(length);
for (var i = 0; i < length; i++) {
var derivativeType = derivativeTypes[i];
if (derivativeType === Number) {
derivativeType = PackableNumber;
}
var derivativePackedLength = derivativeType.packedLength;
packedLength += derivativePackedLength;
packedInterpolationLength += defaultValue(derivativeType.packedInterpolationLength, derivativePackedLength);
innerDerivativeTypes[i] = derivativeType;
}
inputOrder = length;
}
this._type = type;
this._innerType = innerType;
this._interpolationDegree = 1;
this._interpolationAlgorithm = LinearApproximation;
this._numberOfPoints = 0;
this._times = [];
this._values = [];
this._xTable = [];
this._yTable = [];
this._packedLength = packedLength;
this._packedInterpolationLength = packedInterpolationLength;
this._updateTableLength = true;
this._interpolationResult = new Array(packedInterpolationLength);
this._definitionChanged = new Event();
this._derivativeTypes = derivativeTypes;
this._innerDerivativeTypes = innerDerivativeTypes;
this._inputOrder = inputOrder;
this._forwardExtrapolationType = ExtrapolationType.NONE;
this._forwardExtrapolationDuration = 0;
this._backwardExtrapolationType = ExtrapolationType.NONE;
this._backwardExtrapolationDuration = 0;
}
defineProperties(SampledProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof SampledProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._values.length === 0;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof SampledProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the type of property.
* @memberof SampledProperty.prototype
* @type {Object}
*/
type : {
get : function() {
return this._type;
}
},
/**
* Gets the derivative types used by this property.
* @memberof SampledProperty.prototype
* @type {Packable[]}
*/
derivativeTypes : {
get : function() {
return this._derivativeTypes;
}
},
/**
* Gets the degree of interpolation to perform when retrieving a value.
* @memberof SampledProperty.prototype
* @type {Number}
* @default 1
*/
interpolationDegree : {
get : function() {
return this._interpolationDegree;
}
},
/**
* Gets the interpolation algorithm to use when retrieving a value.
* @memberof SampledProperty.prototype
* @type {InterpolationAlgorithm}
* @default LinearApproximation
*/
interpolationAlgorithm : {
get : function() {
return this._interpolationAlgorithm;
}
},
/**
* Gets or sets the type of extrapolation to perform when a value
* is requested at a time after any available samples.
* @memberof SampledProperty.prototype
* @type {ExtrapolationType}
* @default ExtrapolationType.NONE
*/
forwardExtrapolationType : {
get : function() {
return this._forwardExtrapolationType;
},
set : function(value) {
if (this._forwardExtrapolationType !== value) {
this._forwardExtrapolationType = value;
this._definitionChanged.raiseEvent(this);
}
}
},
/**
* Gets or sets the amount of time to extrapolate forward before
* the property becomes undefined. A value of 0 will extrapolate forever.
* @memberof SampledProperty.prototype
* @type {Number}
* @default 0
*/
forwardExtrapolationDuration : {
get : function() {
return this._forwardExtrapolationDuration;
},
set : function(value) {
if (this._forwardExtrapolationDuration !== value) {
this._forwardExtrapolationDuration = value;
this._definitionChanged.raiseEvent(this);
}
}
},
/**
* Gets or sets the type of extrapolation to perform when a value
* is requested at a time before any available samples.
* @memberof SampledProperty.prototype
* @type {ExtrapolationType}
* @default ExtrapolationType.NONE
*/
backwardExtrapolationType : {
get : function() {
return this._backwardExtrapolationType;
},
set : function(value) {
if (this._backwardExtrapolationType !== value) {
this._backwardExtrapolationType = value;
this._definitionChanged.raiseEvent(this);
}
}
},
/**
* Gets or sets the amount of time to extrapolate backward
* before the property becomes undefined. A value of 0 will extrapolate forever.
* @memberof SampledProperty.prototype
* @type {Number}
* @default 0
*/
backwardExtrapolationDuration : {
get : function() {
return this._backwardExtrapolationDuration;
},
set : function(value) {
if (this._backwardExtrapolationDuration !== value) {
this._backwardExtrapolationDuration = value;
this._definitionChanged.raiseEvent(this);
}
}
}
});
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
SampledProperty.prototype.getValue = function(time, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var times = this._times;
var timesLength = times.length;
if (timesLength === 0) {
return undefined;
}
var timeout;
var innerType = this._innerType;
var values = this._values;
var index = binarySearch(times, time, JulianDate.compare);
if (index < 0) {
index = ~index;
if (index === 0) {
var startTime = times[index];
timeout = this._backwardExtrapolationDuration;
if (this._backwardExtrapolationType === ExtrapolationType.NONE || (timeout !== 0 && JulianDate.secondsDifference(startTime, time) > timeout)) {
return undefined;
}
if (this._backwardExtrapolationType === ExtrapolationType.HOLD) {
return innerType.unpack(values, 0, result);
}
}
if (index >= timesLength) {
index = timesLength - 1;
var endTime = times[index];
timeout = this._forwardExtrapolationDuration;
if (this._forwardExtrapolationType === ExtrapolationType.NONE || (timeout !== 0 && JulianDate.secondsDifference(time, endTime) > timeout)) {
return undefined;
}
if (this._forwardExtrapolationType === ExtrapolationType.HOLD) {
index = timesLength - 1;
return innerType.unpack(values, index * innerType.packedLength, result);
}
}
var xTable = this._xTable;
var yTable = this._yTable;
var interpolationAlgorithm = this._interpolationAlgorithm;
var packedInterpolationLength = this._packedInterpolationLength;
var inputOrder = this._inputOrder;
if (this._updateTableLength) {
this._updateTableLength = false;
var numberOfPoints = Math.min(interpolationAlgorithm.getRequiredDataPoints(this._interpolationDegree, inputOrder), timesLength);
if (numberOfPoints !== this._numberOfPoints) {
this._numberOfPoints = numberOfPoints;
xTable.length = numberOfPoints;
yTable.length = numberOfPoints * packedInterpolationLength;
}
}
var degree = this._numberOfPoints - 1;
if (degree < 1) {
return undefined;
}
var firstIndex = 0;
var lastIndex = timesLength - 1;
var pointsInCollection = lastIndex - firstIndex + 1;
if (pointsInCollection >= degree + 1) {
var computedFirstIndex = index - ((degree / 2) | 0) - 1;
if (computedFirstIndex < firstIndex) {
computedFirstIndex = firstIndex;
}
var computedLastIndex = computedFirstIndex + degree;
if (computedLastIndex > lastIndex) {
computedLastIndex = lastIndex;
computedFirstIndex = computedLastIndex - degree;
if (computedFirstIndex < firstIndex) {
computedFirstIndex = firstIndex;
}
}
firstIndex = computedFirstIndex;
lastIndex = computedLastIndex;
}
var length = lastIndex - firstIndex + 1;
// Build the tables
for (var i = 0; i < length; ++i) {
xTable[i] = JulianDate.secondsDifference(times[firstIndex + i], times[lastIndex]);
}
if (!defined(innerType.convertPackedArrayForInterpolation)) {
var destinationIndex = 0;
var packedLength = this._packedLength;
var sourceIndex = firstIndex * packedLength;
var stop = (lastIndex + 1) * packedLength;
while (sourceIndex < stop) {
yTable[destinationIndex] = values[sourceIndex];
sourceIndex++;
destinationIndex++;
}
} else {
innerType.convertPackedArrayForInterpolation(values, firstIndex, lastIndex, yTable);
}
// Interpolate!
var x = JulianDate.secondsDifference(time, times[lastIndex]);
var interpolationResult;
if (inputOrder === 0 || !defined(interpolationAlgorithm.interpolate)) {
interpolationResult = interpolationAlgorithm.interpolateOrderZero(x, xTable, yTable, packedInterpolationLength, this._interpolationResult);
} else {
var yStride = Math.floor(packedInterpolationLength / (inputOrder + 1));
interpolationResult = interpolationAlgorithm.interpolate(x, xTable, yTable, yStride, inputOrder, inputOrder, this._interpolationResult);
}
if (!defined(innerType.unpackInterpolationResult)) {
return innerType.unpack(interpolationResult, 0, result);
}
return innerType.unpackInterpolationResult(interpolationResult, values, firstIndex, lastIndex, result);
}
return innerType.unpack(values, index * this._packedLength, result);
};
/**
* Sets the algorithm and degree to use when interpolating a value.
*
* @param {Object} [options] Object with the following properties:
* @param {InterpolationAlgorithm} [options.interpolationAlgorithm] The new interpolation algorithm. If undefined, the existing property will be unchanged.
* @param {Number} [options.interpolationDegree] The new interpolation degree. If undefined, the existing property will be unchanged.
*/
SampledProperty.prototype.setInterpolationOptions = function(options) {
if (!defined(options)) {
throw new DeveloperError('options is required.');
}
var valuesChanged = false;
var interpolationAlgorithm = options.interpolationAlgorithm;
var interpolationDegree = options.interpolationDegree;
if (this._interpolationAlgorithm !== interpolationAlgorithm) {
this._interpolationAlgorithm = interpolationAlgorithm;
valuesChanged = true;
}
if (this._interpolationDegree !== interpolationDegree) {
this._interpolationDegree = interpolationDegree;
valuesChanged = true;
}
if (valuesChanged) {
this._updateTableLength = true;
this._definitionChanged.raiseEvent(this);
}
};
/**
* Adds a new sample
*
* @param {JulianDate} time The sample time.
* @param {Packable} value The value at the provided time.
* @param {Packable[]} [derivatives] The array of derivatives at the provided time.
*/
SampledProperty.prototype.addSample = function(time, value, derivatives) {
var innerDerivativeTypes = this._innerDerivativeTypes;
var hasDerivatives = defined(innerDerivativeTypes);
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (hasDerivatives && !defined(derivatives)) {
throw new DeveloperError('derivatives is required.');
}
var innerType = this._innerType;
var data = [];
data.push(time);
innerType.pack(value, data, data.length);
if (hasDerivatives) {
var derivativesLength = innerDerivativeTypes.length;
for (var x = 0; x < derivativesLength; x++) {
innerDerivativeTypes[x].pack(derivatives[x], data, data.length);
}
}
mergeNewSamples(undefined, this._times, this._values, data, this._packedLength);
this._updateTableLength = true;
this._definitionChanged.raiseEvent(this);
};
/**
* Adds an array of samples
*
* @param {JulianDate[]} times An array of JulianDate instances where each index is a sample time.
* @param {Packable[]} values The array of values, where each value corresponds to the provided times index.
* @param {Array[]} [derivativeValues] An array where each item is the array of derivatives at the equivalent time index.
*
* @exception {DeveloperError} times and values must be the same length.
* @exception {DeveloperError} times and derivativeValues must be the same length.
*/
SampledProperty.prototype.addSamples = function(times, values, derivativeValues) {
var innerDerivativeTypes = this._innerDerivativeTypes;
var hasDerivatives = defined(innerDerivativeTypes);
if (!defined(times)) {
throw new DeveloperError('times is required.');
}
if (!defined(values)) {
throw new DeveloperError('values is required.');
}
if (times.length !== values.length) {
throw new DeveloperError('times and values must be the same length.');
}
if (hasDerivatives && (!defined(derivativeValues) || derivativeValues.length !== times.length)) {
throw new DeveloperError('times and derivativeValues must be the same length.');
}
var innerType = this._innerType;
var length = times.length;
var data = [];
for (var i = 0; i < length; i++) {
data.push(times[i]);
innerType.pack(values[i], data, data.length);
if (hasDerivatives) {
var derivatives = derivativeValues[i];
var derivativesLength = innerDerivativeTypes.length;
for (var x = 0; x < derivativesLength; x++) {
innerDerivativeTypes[x].pack(derivatives[x], data, data.length);
}
}
}
mergeNewSamples(undefined, this._times, this._values, data, this._packedLength);
this._updateTableLength = true;
this._definitionChanged.raiseEvent(this);
};
/**
* Adds samples as a single packed array where each new sample is represented as a date,
* followed by the packed representation of the corresponding value and derivatives.
*
* @param {Number[]} packedSamples The array of packed samples.
* @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
*/
SampledProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) {
if (!defined(packedSamples)) {
throw new DeveloperError('packedSamples is required.');
}
mergeNewSamples(epoch, this._times, this._values, packedSamples, this._packedLength);
this._updateTableLength = true;
this._definitionChanged.raiseEvent(this);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
SampledProperty.prototype.equals = function(other) {
if (this === other) {
return true;
}
if (!defined(other)) {
return false;
}
if (this._type !== other._type || //
this._interpolationDegree !== other._interpolationDegree || //
this._interpolationAlgorithm !== other._interpolationAlgorithm) {
return false;
}
var derivativeTypes = this._derivativeTypes;
var hasDerivatives = defined(derivativeTypes);
var otherDerivativeTypes = other._derivativeTypes;
var otherHasDerivatives = defined(otherDerivativeTypes);
if (hasDerivatives !== otherHasDerivatives) {
return false;
}
var i;
var length;
if (hasDerivatives) {
length = derivativeTypes.length;
if (length !== otherDerivativeTypes.length) {
return false;
}
for (i = 0; i < length; i++) {
if (derivativeTypes[i] !== otherDerivativeTypes[i]) {
return false;
}
}
}
var times = this._times;
var otherTimes = other._times;
length = times.length;
if (length !== otherTimes.length) {
return false;
}
for (i = 0; i < length; i++) {
if (!JulianDate.equals(times[i], otherTimes[i])) {
return false;
}
}
var values = this._values;
var otherValues = other._values;
for (i = 0; i < length; i++) {
if (values[i] !== otherValues[i]) {
return false;
}
}
return true;
};
//Exposed for testing.
SampledProperty._mergeNewSamples = mergeNewSamples;
return SampledProperty;
});
/*global define*/
define('DataSources/SampledPositionProperty',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/ReferenceFrame',
'./PositionProperty',
'./Property',
'./SampledProperty'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
ReferenceFrame,
PositionProperty,
Property,
SampledProperty) {
'use strict';
/**
* A {@link SampledProperty} which is also a {@link PositionProperty}.
*
* @alias SampledPositionProperty
* @constructor
*
* @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
* @param {Number} [numberOfDerivatives=0] The number of derivatives that accompany each position; i.e. velocity, acceleration, etc...
*/
function SampledPositionProperty(referenceFrame, numberOfDerivatives) {
numberOfDerivatives = defaultValue(numberOfDerivatives, 0);
var derivativeTypes;
if (numberOfDerivatives > 0) {
derivativeTypes = new Array(numberOfDerivatives);
for (var i = 0; i < numberOfDerivatives; i++) {
derivativeTypes[i] = Cartesian3;
}
}
this._numberOfDerivatives = numberOfDerivatives;
this._property = new SampledProperty(Cartesian3, derivativeTypes);
this._definitionChanged = new Event();
this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
this._property._definitionChanged.addEventListener(function() {
this._definitionChanged.raiseEvent(this);
}, this);
}
defineProperties(SampledPositionProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof SampledPositionProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._property.isConstant;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof SampledPositionProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the reference frame in which the position is defined.
* @memberof SampledPositionProperty.prototype
* @type {ReferenceFrame}
* @default ReferenceFrame.FIXED;
*/
referenceFrame : {
get : function() {
return this._referenceFrame;
}
},
/**
* Gets the degree of interpolation to perform when retrieving a value.
* @memberof SampledPositionProperty.prototype
*
* @type {Number}
* @default 1
*/
interpolationDegree : {
get : function() {
return this._property.interpolationDegree;
}
},
/**
* Gets the interpolation algorithm to use when retrieving a value.
* @memberof SampledPositionProperty.prototype
*
* @type {InterpolationAlgorithm}
* @default LinearApproximation
*/
interpolationAlgorithm : {
get : function() {
return this._property.interpolationAlgorithm;
}
},
/**
* The number of derivatives contained by this property; i.e. 0 for just position, 1 for velocity, etc.
* @memberof SampledPositionProperty.prototype
*
* @type {Boolean}
* @default false
*/
numberOfDerivatives : {
get : function() {
return this._numberOfDerivatives;
}
},
/**
* Gets or sets the type of extrapolation to perform when a value
* is requested at a time after any available samples.
* @memberof SampledPositionProperty.prototype
* @type {ExtrapolationType}
* @default ExtrapolationType.NONE
*/
forwardExtrapolationType : {
get : function() {
return this._property.forwardExtrapolationType;
},
set : function(value) {
this._property.forwardExtrapolationType = value;
}
},
/**
* Gets or sets the amount of time to extrapolate forward before
* the property becomes undefined. A value of 0 will extrapolate forever.
* @memberof SampledPositionProperty.prototype
* @type {Number}
* @default 0
*/
forwardExtrapolationDuration : {
get : function() {
return this._property.forwardExtrapolationDuration;
},
set : function(value) {
this._property.forwardExtrapolationDuration = value;
}
},
/**
* Gets or sets the type of extrapolation to perform when a value
* is requested at a time before any available samples.
* @memberof SampledPositionProperty.prototype
* @type {ExtrapolationType}
* @default ExtrapolationType.NONE
*/
backwardExtrapolationType : {
get : function() {
return this._property.backwardExtrapolationType;
},
set : function(value) {
this._property.backwardExtrapolationType = value;
}
},
/**
* Gets or sets the amount of time to extrapolate backward
* before the property becomes undefined. A value of 0 will extrapolate forever.
* @memberof SampledPositionProperty.prototype
* @type {Number}
* @default 0
*/
backwardExtrapolationDuration : {
get : function() {
return this._property.backwardExtrapolationDuration;
},
set : function(value) {
this._property.backwardExtrapolationDuration = value;
}
}
});
/**
* Gets the position at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
SampledPositionProperty.prototype.getValue = function(time, result) {
return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
};
/**
* Gets the position at the provided time and in the provided reference frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
SampledPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(referenceFrame)) {
throw new DeveloperError('referenceFrame is required.');
}
result = this._property.getValue(time, result);
if (defined(result)) {
return PositionProperty.convertToReferenceFrame(time, result, this._referenceFrame, referenceFrame, result);
}
return undefined;
};
/**
* Sets the algorithm and degree to use when interpolating a position.
*
* @param {Object} [options] Object with the following properties:
* @param {InterpolationAlgorithm} [options.interpolationAlgorithm] The new interpolation algorithm. If undefined, the existing property will be unchanged.
* @param {Number} [options.interpolationDegree] The new interpolation degree. If undefined, the existing property will be unchanged.
*/
SampledPositionProperty.prototype.setInterpolationOptions = function(options) {
this._property.setInterpolationOptions(options);
};
/**
* Adds a new sample.
*
* @param {JulianDate} time The sample time.
* @param {Cartesian3} position The position at the provided time.
* @param {Cartesian3[]} [derivatives] The array of derivative values at the provided time.
*/
SampledPositionProperty.prototype.addSample = function(time, position, derivatives) {
var numberOfDerivatives = this._numberOfDerivatives;
if (numberOfDerivatives > 0 && (!defined(derivatives) || derivatives.length !== numberOfDerivatives)) {
throw new DeveloperError('derivatives length must be equal to the number of derivatives.');
}
this._property.addSample(time, position, derivatives);
};
/**
* Adds multiple samples via parallel arrays.
*
* @param {JulianDate[]} times An array of JulianDate instances where each index is a sample time.
* @param {Cartesian3[]} positions An array of Cartesian3 position instances, where each value corresponds to the provided time index.
* @param {Array[]} [derivatives] An array where each value is another array containing derivatives for the corresponding time index.
*
* @exception {DeveloperError} All arrays must be the same length.
*/
SampledPositionProperty.prototype.addSamples = function(times, positions, derivatives) {
this._property.addSamples(times, positions, derivatives);
};
/**
* Adds samples as a single packed array where each new sample is represented as a date,
* followed by the packed representation of the corresponding value and derivatives.
*
* @param {Number[]} packedSamples The array of packed samples.
* @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
*/
SampledPositionProperty.prototype.addSamplesPackedArray = function(data, epoch) {
this._property.addSamplesPackedArray(data, epoch);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
SampledPositionProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof SampledPositionProperty &&
Property.equals(this._property, other._property) && //
this._referenceFrame === other._referenceFrame);
};
return SampledPositionProperty;
});
/*global define*/
define('DataSources/StripeOrientation',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Defined the orientation of stripes in {@link StripeMaterialProperty}.
*
* @exports StripeOrientation
*/
var StripeOrientation = {
/**
* Horizontal orientation.
* @type {Number}
*/
HORIZONTAL : 0,
/**
* Vertical orientation.
* @type {Number}
*/
VERTICAL : 1
};
return freezeObject(StripeOrientation);
});
/*global define*/
define('DataSources/StripeMaterialProperty',[
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'./createPropertyDescriptor',
'./Property',
'./StripeOrientation'
], function(
Color,
defaultValue,
defined,
defineProperties,
Event,
createPropertyDescriptor,
Property,
StripeOrientation) {
'use strict';
var defaultOrientation = StripeOrientation.HORIZONTAL;
var defaultEvenColor = Color.WHITE;
var defaultOddColor = Color.BLACK;
var defaultOffset = 0;
var defaultRepeat = 1;
/**
* A {@link MaterialProperty} that maps to stripe {@link Material} uniforms.
* @alias StripeMaterialProperty
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Property} [options.evenColor=Color.WHITE] A Property specifying the first {@link Color}.
* @param {Property} [options.oddColor=Color.BLACK] A Property specifying the second {@link Color}.
* @param {Property} [options.repeat=1] A numeric Property specifying how many times the stripes repeat.
* @param {Property} [options.offset=0] A numeric Property specifying how far into the pattern to start the material.
* @param {Property} [options.orientation=StripeOrientation.HORIZONTAL] A Property specifying the {@link StripeOrientation}.
*/
function StripeMaterialProperty(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Event();
this._orientation = undefined;
this._orientationSubscription = undefined;
this._evenColor = undefined;
this._evenColorSubscription = undefined;
this._oddColor = undefined;
this._oddColorSubscription = undefined;
this._offset = undefined;
this._offsetSubscription = undefined;
this._repeat = undefined;
this._repeatSubscription = undefined;
this.orientation = options.orientation;
this.evenColor = options.evenColor;
this.oddColor = options.oddColor;
this.offset = options.offset;
this.repeat = options.repeat;
}
defineProperties(StripeMaterialProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof StripeMaterialProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._orientation) && //
Property.isConstant(this._evenColor) && //
Property.isConstant(this._oddColor) && //
Property.isConstant(this._offset) && //
Property.isConstant(this._repeat);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof StripeMaterialProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the Property specifying the {@link StripeOrientation}/
* @memberof StripeMaterialProperty.prototype
* @type {Property}
* @default StripeOrientation.HORIZONTAL
*/
orientation : createPropertyDescriptor('orientation'),
/**
* Gets or sets the Property specifying the first {@link Color}.
* @memberof StripeMaterialProperty.prototype
* @type {Property}
* @default Color.WHITE
*/
evenColor : createPropertyDescriptor('evenColor'),
/**
* Gets or sets the Property specifying the second {@link Color}.
* @memberof StripeMaterialProperty.prototype
* @type {Property}
* @default Color.BLACK
*/
oddColor : createPropertyDescriptor('oddColor'),
/**
* Gets or sets the numeric Property specifying the point into the pattern
* to begin drawing; with 0.0 being the beginning of the even color, 1.0 the beginning
* of the odd color, 2.0 being the even color again, and any multiple or fractional values
* being in between.
* @memberof StripeMaterialProperty.prototype
* @type {Property}
* @default 0.0
*/
offset : createPropertyDescriptor('offset'),
/**
* Gets or sets the numeric Property specifying how many times the stripes repeat.
* @memberof StripeMaterialProperty.prototype
* @type {Property}
* @default 1.0
*/
repeat : createPropertyDescriptor('repeat')
});
/**
* Gets the {@link Material} type at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the type.
* @returns {String} The type of material.
*/
StripeMaterialProperty.prototype.getType = function(time) {
return 'Stripe';
};
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
StripeMaterialProperty.prototype.getValue = function(time, result) {
if (!defined(result)) {
result = {};
}
result.horizontal = Property.getValueOrDefault(this._orientation, time, defaultOrientation) === StripeOrientation.HORIZONTAL;
result.evenColor = Property.getValueOrClonedDefault(this._evenColor, time, defaultEvenColor, result.evenColor);
result.oddColor = Property.getValueOrClonedDefault(this._oddColor, time, defaultOddColor, result.oddColor);
result.offset = Property.getValueOrDefault(this._offset, time, defaultOffset);
result.repeat = Property.getValueOrDefault(this._repeat, time, defaultRepeat);
return result;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
StripeMaterialProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof StripeMaterialProperty && //
Property.equals(this._orientation, other._orientation) && //
Property.equals(this._evenColor, other._evenColor) && //
Property.equals(this._oddColor, other._oddColor) && //
Property.equals(this._offset, other._offset) && //
Property.equals(this._repeat, other._repeat));
};
return StripeMaterialProperty;
});
/*global define*/
define('DataSources/TimeIntervalCollectionPositionProperty',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/ReferenceFrame',
'../Core/TimeIntervalCollection',
'./PositionProperty',
'./Property'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
ReferenceFrame,
TimeIntervalCollection,
PositionProperty,
Property) {
'use strict';
/**
* A {@link TimeIntervalCollectionProperty} which is also a {@link PositionProperty}.
*
* @alias TimeIntervalCollectionPositionProperty
* @constructor
*
* @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
*/
function TimeIntervalCollectionPositionProperty(referenceFrame) {
this._definitionChanged = new Event();
this._intervals = new TimeIntervalCollection();
this._intervals.changedEvent.addEventListener(TimeIntervalCollectionPositionProperty.prototype._intervalsChanged, this);
this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
}
defineProperties(TimeIntervalCollectionPositionProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof TimeIntervalCollectionPositionProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._intervals.isEmpty;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is considered to have changed if a call to getValue would return
* a different result for the same time.
* @memberof TimeIntervalCollectionPositionProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the interval collection.
* @memberof TimeIntervalCollectionPositionProperty.prototype
* @type {TimeIntervalCollection}
*/
intervals : {
get : function() {
return this._intervals;
}
},
/**
* Gets the reference frame in which the position is defined.
* @memberof TimeIntervalCollectionPositionProperty.prototype
* @type {ReferenceFrame}
* @default ReferenceFrame.FIXED;
*/
referenceFrame : {
get : function() {
return this._referenceFrame;
}
}
});
/**
* Gets the value of the property at the provided time in the fixed frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
TimeIntervalCollectionPositionProperty.prototype.getValue = function(time, result) {
return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
};
/**
* Gets the value of the property at the provided time and in the provided reference frame.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
TimeIntervalCollectionPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(referenceFrame)) {
throw new DeveloperError('referenceFrame is required.');
}
var position = this._intervals.findDataForIntervalContainingDate(time);
if (defined(position)) {
return PositionProperty.convertToReferenceFrame(time, position, this._referenceFrame, referenceFrame, result);
}
return undefined;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
TimeIntervalCollectionPositionProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof TimeIntervalCollectionPositionProperty && //
this._intervals.equals(other._intervals, Property.equals) && //
this._referenceFrame === other._referenceFrame);
};
/**
* @private
*/
TimeIntervalCollectionPositionProperty.prototype._intervalsChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return TimeIntervalCollectionPositionProperty;
});
/*global define*/
define('DataSources/TimeIntervalCollectionProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/TimeIntervalCollection',
'./Property'
], function(
defined,
defineProperties,
DeveloperError,
Event,
TimeIntervalCollection,
Property) {
'use strict';
/**
* A {@link Property} which is defined by a {@link TimeIntervalCollection}, where the
* data property of each {@link TimeInterval} represents the value at time.
*
* @alias TimeIntervalCollectionProperty
* @constructor
*
* @example
* //Create a Cartesian2 interval property which contains data on August 1st, 2012
* //and uses a different value every 6 hours.
* var composite = new Cesium.TimeIntervalCollectionProperty();
* composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
* iso8601 : '2012-08-01T00:00:00.00Z/2012-08-01T06:00:00.00Z',
* isStartIncluded : true,
* isStopIncluded : false,
* data : new Cesium.Cartesian2(2.0, 3.4)
* }));
* composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
* iso8601 : '2012-08-01T06:00:00.00Z/2012-08-01T12:00:00.00Z',
* isStartIncluded : true,
* isStopIncluded : false,
* data : new Cesium.Cartesian2(12.0, 2.7)
* }));
* composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
* iso8601 : '2012-08-01T12:00:00.00Z/2012-08-01T18:00:00.00Z',
* isStartIncluded : true,
* isStopIncluded : false,
* data : new Cesium.Cartesian2(5.0, 12.4)
* }));
* composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
* iso8601 : '2012-08-01T18:00:00.00Z/2012-08-02T00:00:00.00Z',
* isStartIncluded : true,
* isStopIncluded : true,
* data : new Cesium.Cartesian2(85.0, 4.1)
* }));
*/
function TimeIntervalCollectionProperty() {
this._definitionChanged = new Event();
this._intervals = new TimeIntervalCollection();
this._intervals.changedEvent.addEventListener(TimeIntervalCollectionProperty.prototype._intervalsChanged, this);
}
defineProperties(TimeIntervalCollectionProperty.prototype, {
/**
* Gets a value indicating if this property is constant. A property is considered
* constant if getValue always returns the same result for the current definition.
* @memberof TimeIntervalCollectionProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return this._intervals.isEmpty;
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* The definition is changed whenever setValue is called with data different
* than the current value.
* @memberof TimeIntervalCollectionProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets the interval collection.
* @memberof TimeIntervalCollectionProperty.prototype
*
* @type {TimeIntervalCollection}
*/
intervals : {
get : function() {
return this._intervals;
}
}
});
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} time The time for which to retrieve the value.
* @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
*/
TimeIntervalCollectionProperty.prototype.getValue = function(time, result) {
if (!defined(time)) {
throw new DeveloperError('time is required');
}
var value = this._intervals.findDataForIntervalContainingDate(time);
if (defined(value) && (typeof value.clone === 'function')) {
return value.clone(result);
}
return value;
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
TimeIntervalCollectionProperty.prototype.equals = function(other) {
return this === other || //
(other instanceof TimeIntervalCollectionProperty && //
this._intervals.equals(other._intervals, Property.equals));
};
/**
* @private
*/
TimeIntervalCollectionProperty.prototype._intervalsChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return TimeIntervalCollectionProperty;
});
/*global define*/
define('DataSources/VelocityVectorProperty',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/JulianDate',
'./Property'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
JulianDate,
Property) {
'use strict';
/**
* A {@link Property} which evaluates to a {@link Cartesian3} vector
* based on the velocity of the provided {@link PositionProperty}.
*
* @alias VelocityVectorProperty
* @constructor
*
* @param {Property} [position] The position property used to compute the velocity.
* @param {Boolean} [normalize=true] Whether to normalize the computed velocity vector.
*
* @example
* //Create an entity with a billboard rotated to match its velocity.
* var position = new Cesium.SampledProperty();
* position.addSamples(...);
* var entity = viewer.entities.add({
* position : position,
* billboard : {
* image : 'image.png',
* alignedAxis : new Cesium.VelocityVectorProperty(position, true) // alignedAxis must be a unit vector
* }
* }));
*/
function VelocityVectorProperty(position, normalize) {
this._position = undefined;
this._subscription = undefined;
this._definitionChanged = new Event();
this._normalize = defaultValue(normalize, true);
this.position = position;
}
defineProperties(VelocityVectorProperty.prototype, {
/**
* Gets a value indicating if this property is constant.
* @memberof VelocityVectorProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._position);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* @memberof VelocityVectorProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the position property used to compute the velocity vector.
* @memberof VelocityVectorProperty.prototype
*
* @type {Property}
*/
position : {
get : function() {
return this._position;
},
set : function(value) {
var oldValue = this._position;
if (oldValue !== value) {
if (defined(oldValue)) {
this._subscription();
}
this._position = value;
if (defined(value)) {
this._subscription = value._definitionChanged.addEventListener(function() {
this._definitionChanged.raiseEvent(this);
}, this);
}
this._definitionChanged.raiseEvent(this);
}
}
},
/**
* Gets or sets whether the vector produced by this property
* will be normalized or not.
* @memberof VelocityVectorProperty.prototype
*
* @type {Boolean}
*/
normalize : {
get : function() {
return this._normalize;
},
set : function(value) {
if (this._normalize === value) {
return;
}
this._normalize = value;
this._definitionChanged.raiseEvent(this);
}
}
});
var position1Scratch = new Cartesian3();
var position2Scratch = new Cartesian3();
var timeScratch = new JulianDate();
var step = 1.0 / 60.0;
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} [time] The time for which to retrieve the value.
* @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
*/
VelocityVectorProperty.prototype.getValue = function(time, result) {
return this._getValue(time, result);
};
/**
* @private
*/
VelocityVectorProperty.prototype._getValue = function(time, velocityResult, positionResult) {
if (!defined(time)) {
throw new DeveloperError('time is required');
}
if (!defined(velocityResult)) {
velocityResult = new Cartesian3();
}
var property = this._position;
if (Property.isConstant(property)) {
return this._normalize ? undefined : Cartesian3.clone(Cartesian3.ZERO, velocityResult);
}
var position1 = property.getValue(time, position1Scratch);
var position2 = property.getValue(JulianDate.addSeconds(time, step, timeScratch), position2Scratch);
//If we don't have a position for now, return undefined.
if (!defined(position1)) {
return undefined;
}
//If we don't have a position for now + step, see if we have a position for now - step.
if (!defined(position2)) {
position2 = position1;
position1 = property.getValue(JulianDate.addSeconds(time, -step, timeScratch), position2Scratch);
if (!defined(position1)) {
return undefined;
}
}
if (Cartesian3.equals(position1, position2)) {
return this._normalize ? undefined : Cartesian3.clone(Cartesian3.ZERO, velocityResult);
}
if (defined(positionResult)) {
position1.clone(positionResult);
}
var velocity = Cartesian3.subtract(position2, position1, velocityResult);
if (this._normalize) {
return Cartesian3.normalize(velocity, velocityResult);
} else {
return Cartesian3.divideByScalar(velocity, step, velocityResult);
}
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
VelocityVectorProperty.prototype.equals = function(other) {
return this === other ||//
(other instanceof VelocityVectorProperty &&
Property.equals(this._position, other._position));
};
return VelocityVectorProperty;
});
/*global define*/
define('DataSources/CzmlDataSource',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/ClockRange',
'../Core/ClockStep',
'../Core/Color',
'../Core/CornerType',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/Event',
'../Core/ExtrapolationType',
'../Core/getAbsoluteUri',
'../Core/getFilenameFromUri',
'../Core/HermitePolynomialApproximation',
'../Core/isArray',
'../Core/Iso8601',
'../Core/JulianDate',
'../Core/LagrangePolynomialApproximation',
'../Core/LinearApproximation',
'../Core/loadJson',
'../Core/Math',
'../Core/NearFarScalar',
'../Core/Quaternion',
'../Core/Rectangle',
'../Core/ReferenceFrame',
'../Core/RuntimeError',
'../Core/Spherical',
'../Core/TimeInterval',
'../Core/TimeIntervalCollection',
'../Scene/ColorBlendMode',
'../Scene/HeightReference',
'../Scene/HorizontalOrigin',
'../Scene/LabelStyle',
'../Scene/ShadowMode',
'../Scene/VerticalOrigin',
'../ThirdParty/Uri',
'../ThirdParty/when',
'./BillboardGraphics',
'./BoxGraphics',
'./ColorMaterialProperty',
'./CompositeMaterialProperty',
'./CompositePositionProperty',
'./CompositeProperty',
'./ConstantPositionProperty',
'./ConstantProperty',
'./CorridorGraphics',
'./CylinderGraphics',
'./DataSource',
'./DataSourceClock',
'./EllipseGraphics',
'./EllipsoidGraphics',
'./EntityCluster',
'./EntityCollection',
'./GridMaterialProperty',
'./ImageMaterialProperty',
'./LabelGraphics',
'./ModelGraphics',
'./NodeTransformationProperty',
'./PathGraphics',
'./PointGraphics',
'./PolygonGraphics',
'./PolylineArrowMaterialProperty',
'./PolylineGlowMaterialProperty',
'./PolylineGraphics',
'./PolylineOutlineMaterialProperty',
'./PositionPropertyArray',
'./PropertyArray',
'./PropertyBag',
'./RectangleGraphics',
'./ReferenceProperty',
'./Rotation',
'./SampledPositionProperty',
'./SampledProperty',
'./StripeMaterialProperty',
'./StripeOrientation',
'./TimeIntervalCollectionPositionProperty',
'./TimeIntervalCollectionProperty',
'./VelocityVectorProperty',
'./WallGraphics'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
Cartographic,
ClockRange,
ClockStep,
Color,
CornerType,
createGuid,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Event,
ExtrapolationType,
getAbsoluteUri,
getFilenameFromUri,
HermitePolynomialApproximation,
isArray,
Iso8601,
JulianDate,
LagrangePolynomialApproximation,
LinearApproximation,
loadJson,
CesiumMath,
NearFarScalar,
Quaternion,
Rectangle,
ReferenceFrame,
RuntimeError,
Spherical,
TimeInterval,
TimeIntervalCollection,
ColorBlendMode,
HeightReference,
HorizontalOrigin,
LabelStyle,
ShadowMode,
VerticalOrigin,
Uri,
when,
BillboardGraphics,
BoxGraphics,
ColorMaterialProperty,
CompositeMaterialProperty,
CompositePositionProperty,
CompositeProperty,
ConstantPositionProperty,
ConstantProperty,
CorridorGraphics,
CylinderGraphics,
DataSource,
DataSourceClock,
EllipseGraphics,
EllipsoidGraphics,
EntityCluster,
EntityCollection,
GridMaterialProperty,
ImageMaterialProperty,
LabelGraphics,
ModelGraphics,
NodeTransformationProperty,
PathGraphics,
PointGraphics,
PolygonGraphics,
PolylineArrowMaterialProperty,
PolylineGlowMaterialProperty,
PolylineGraphics,
PolylineOutlineMaterialProperty,
PositionPropertyArray,
PropertyArray,
PropertyBag,
RectangleGraphics,
ReferenceProperty,
Rotation,
SampledPositionProperty,
SampledProperty,
StripeMaterialProperty,
StripeOrientation,
TimeIntervalCollectionPositionProperty,
TimeIntervalCollectionProperty,
VelocityVectorProperty,
WallGraphics) {
'use strict';
var currentId;
function makeReference(collection, referenceString) {
if (referenceString[0] === '#') {
referenceString = currentId + referenceString;
}
return ReferenceProperty.fromString(collection, referenceString);
}
var scratchCartesian = new Cartesian3();
var scratchSpherical = new Spherical();
var scratchCartographic = new Cartographic();
var scratchTimeInterval = new TimeInterval();
function unwrapColorInterval(czmlInterval) {
var rgbaf = czmlInterval.rgbaf;
if (defined(rgbaf)) {
return rgbaf;
}
var rgba = czmlInterval.rgba;
if (!defined(rgba)) {
return undefined;
}
var length = rgba.length;
if (length === Color.packedLength) {
return [Color.byteToFloat(rgba[0]), Color.byteToFloat(rgba[1]), Color.byteToFloat(rgba[2]), Color.byteToFloat(rgba[3])];
}
rgbaf = new Array(length);
for (var i = 0; i < length; i += 5) {
rgbaf[i] = rgba[i];
rgbaf[i + 1] = Color.byteToFloat(rgba[i + 1]);
rgbaf[i + 2] = Color.byteToFloat(rgba[i + 2]);
rgbaf[i + 3] = Color.byteToFloat(rgba[i + 3]);
rgbaf[i + 4] = Color.byteToFloat(rgba[i + 4]);
}
return rgbaf;
}
function unwrapUriInterval(czmlInterval, sourceUri) {
var result = defaultValue(czmlInterval.uri, czmlInterval);
if (defined(sourceUri)) {
result = getAbsoluteUri(result, getAbsoluteUri(sourceUri));
}
return result;
}
function unwrapRectangleInterval(czmlInterval) {
var wsen = czmlInterval.wsen;
if (defined(wsen)) {
return wsen;
}
var wsenDegrees = czmlInterval.wsenDegrees;
if (!defined(wsenDegrees)) {
return undefined;
}
var length = wsenDegrees.length;
if (length === Rectangle.packedLength) {
return [CesiumMath.toRadians(wsenDegrees[0]), CesiumMath.toRadians(wsenDegrees[1]), CesiumMath.toRadians(wsenDegrees[2]), CesiumMath.toRadians(wsenDegrees[3])];
}
wsen = new Array(length);
for (var i = 0; i < length; i += 5) {
wsen[i] = wsenDegrees[i];
wsen[i + 1] = CesiumMath.toRadians(wsenDegrees[i + 1]);
wsen[i + 2] = CesiumMath.toRadians(wsenDegrees[i + 2]);
wsen[i + 3] = CesiumMath.toRadians(wsenDegrees[i + 3]);
wsen[i + 4] = CesiumMath.toRadians(wsenDegrees[i + 4]);
}
return wsen;
}
function convertUnitSphericalToCartesian(unitSpherical) {
var length = unitSpherical.length;
scratchSpherical.magnitude = 1.0;
if (length === 2) {
scratchSpherical.clock = unitSpherical[0];
scratchSpherical.cone = unitSpherical[1];
Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);
return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
} else {
var result = new Array(length / 3 * 4);
for (var i = 0, j = 0; i < length; i += 3, j += 4) {
result[j] = unitSpherical[i];
scratchSpherical.clock = unitSpherical[i + 1];
scratchSpherical.cone = unitSpherical[i + 2];
Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);
result[j + 1] = scratchCartesian.x;
result[j + 2] = scratchCartesian.y;
result[j + 3] = scratchCartesian.z;
}
return result;
}
}
function convertSphericalToCartesian(spherical) {
var length = spherical.length;
if (length === 3) {
scratchSpherical.clock = spherical[0];
scratchSpherical.cone = spherical[1];
scratchSpherical.magnitude = spherical[2];
Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);
return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
} else {
var result = new Array(length);
for (var i = 0; i < length; i += 4) {
result[i] = spherical[i];
scratchSpherical.clock = spherical[i + 1];
scratchSpherical.cone = spherical[i + 2];
scratchSpherical.magnitude = spherical[i + 3];
Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);
result[i + 1] = scratchCartesian.x;
result[i + 2] = scratchCartesian.y;
result[i + 3] = scratchCartesian.z;
}
return result;
}
}
function convertCartographicRadiansToCartesian(cartographicRadians) {
var length = cartographicRadians.length;
if (length === 3) {
scratchCartographic.longitude = cartographicRadians[0];
scratchCartographic.latitude = cartographicRadians[1];
scratchCartographic.height = cartographicRadians[2];
Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);
return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
} else {
var result = new Array(length);
for (var i = 0; i < length; i += 4) {
result[i] = cartographicRadians[i];
scratchCartographic.longitude = cartographicRadians[i + 1];
scratchCartographic.latitude = cartographicRadians[i + 2];
scratchCartographic.height = cartographicRadians[i + 3];
Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);
result[i + 1] = scratchCartesian.x;
result[i + 2] = scratchCartesian.y;
result[i + 3] = scratchCartesian.z;
}
return result;
}
}
function convertCartographicDegreesToCartesian(cartographicDegrees) {
var length = cartographicDegrees.length;
if (length === 3) {
scratchCartographic.longitude = CesiumMath.toRadians(cartographicDegrees[0]);
scratchCartographic.latitude = CesiumMath.toRadians(cartographicDegrees[1]);
scratchCartographic.height = cartographicDegrees[2];
Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);
return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
} else {
var result = new Array(length);
for (var i = 0; i < length; i += 4) {
result[i] = cartographicDegrees[i];
scratchCartographic.longitude = CesiumMath.toRadians(cartographicDegrees[i + 1]);
scratchCartographic.latitude = CesiumMath.toRadians(cartographicDegrees[i + 2]);
scratchCartographic.height = cartographicDegrees[i + 3];
Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);
result[i + 1] = scratchCartesian.x;
result[i + 2] = scratchCartesian.y;
result[i + 3] = scratchCartesian.z;
}
return result;
}
}
function unwrapCartesianInterval(czmlInterval) {
var cartesian = czmlInterval.cartesian;
if (defined(cartesian)) {
return cartesian;
}
var cartesianVelocity = czmlInterval.cartesianVelocity;
if (defined(cartesianVelocity)) {
return cartesianVelocity;
}
var unitCartesian = czmlInterval.unitCartesian;
if (defined(unitCartesian)) {
return unitCartesian;
}
var unitSpherical = czmlInterval.unitSpherical;
if (defined(unitSpherical)) {
return convertUnitSphericalToCartesian(unitSpherical);
}
var spherical = czmlInterval.spherical;
if (defined(spherical)) {
return convertSphericalToCartesian(spherical);
}
var cartographicRadians = czmlInterval.cartographicRadians;
if (defined(cartographicRadians)) {
return convertCartographicRadiansToCartesian(cartographicRadians);
}
var cartographicDegrees = czmlInterval.cartographicDegrees;
if (defined(cartographicDegrees)) {
return convertCartographicDegreesToCartesian(cartographicDegrees);
}
throw new RuntimeError(JSON.stringify(czmlInterval) + ' is not a valid CZML interval.');
}
function normalizePackedQuaternionArray(array, startingIndex) {
var x = array[startingIndex];
var y = array[startingIndex + 1];
var z = array[startingIndex + 2];
var w = array[startingIndex + 3];
var inverseMagnitude = 1.0 / Math.sqrt(x * x + y * y + z * z + w * w);
array[startingIndex] = x * inverseMagnitude;
array[startingIndex + 1] = y * inverseMagnitude;
array[startingIndex + 2] = z * inverseMagnitude;
array[startingIndex + 3] = w * inverseMagnitude;
}
function unwrapQuaternionInterval(czmlInterval) {
var unitQuaternion = czmlInterval.unitQuaternion;
if (defined(unitQuaternion)) {
if (unitQuaternion.length === 4) {
normalizePackedQuaternionArray(unitQuaternion, 0);
return unitQuaternion;
}
for (var i = 1; i < unitQuaternion.length; i += 5) {
normalizePackedQuaternionArray(unitQuaternion, i);
}
}
return unitQuaternion;
}
function unwrapInterval(type, czmlInterval, sourceUri) {
/*jshint sub:true*/
switch (type) {
case Array:
return czmlInterval.array;
case Boolean:
return defaultValue(czmlInterval['boolean'], czmlInterval);
case BoundingRectangle:
return czmlInterval.boundingRectangle;
case Cartesian2:
return czmlInterval.cartesian2;
case Cartesian3:
return unwrapCartesianInterval(czmlInterval);
case Color:
return unwrapColorInterval(czmlInterval);
case ColorBlendMode:
return ColorBlendMode[defaultValue(czmlInterval.colorBlendMode, czmlInterval)];
case CornerType:
return CornerType[defaultValue(czmlInterval.cornerType, czmlInterval)];
case HeightReference:
return HeightReference[defaultValue(czmlInterval.heightReference, czmlInterval)];
case HorizontalOrigin:
return HorizontalOrigin[defaultValue(czmlInterval.horizontalOrigin, czmlInterval)];
case Image:
return unwrapUriInterval(czmlInterval, sourceUri);
case JulianDate:
return JulianDate.fromIso8601(defaultValue(czmlInterval.date, czmlInterval));
case LabelStyle:
return LabelStyle[defaultValue(czmlInterval.labelStyle, czmlInterval)];
case Number:
return defaultValue(czmlInterval.number, czmlInterval);
case NearFarScalar:
return czmlInterval.nearFarScalar;
case Quaternion:
return unwrapQuaternionInterval(czmlInterval);
case Rotation:
return defaultValue(czmlInterval.number, czmlInterval);
case ShadowMode:
return ShadowMode[defaultValue(czmlInterval.shadows, czmlInterval)];
case String:
return defaultValue(czmlInterval.string, czmlInterval);
case StripeOrientation:
return StripeOrientation[defaultValue(czmlInterval.stripeOrientation, czmlInterval)];
case Rectangle:
return unwrapRectangleInterval(czmlInterval);
case Uri:
return unwrapUriInterval(czmlInterval, sourceUri);
case VerticalOrigin:
return VerticalOrigin[defaultValue(czmlInterval.verticalOrigin, czmlInterval)];
default:
throw new RuntimeError(type);
}
}
var interpolators = {
HERMITE : HermitePolynomialApproximation,
LAGRANGE : LagrangePolynomialApproximation,
LINEAR : LinearApproximation
};
function updateInterpolationSettings(packetData, property) {
var interpolationAlgorithm = packetData.interpolationAlgorithm;
if (defined(interpolationAlgorithm) || defined(packetData.interpolationDegree)) {
property.setInterpolationOptions({
interpolationAlgorithm : interpolators[interpolationAlgorithm],
interpolationDegree : packetData.interpolationDegree
});
}
var forwardExtrapolationType = packetData.forwardExtrapolationType;
if (defined(forwardExtrapolationType)) {
property.forwardExtrapolationType = ExtrapolationType[forwardExtrapolationType];
}
var forwardExtrapolationDuration = packetData.forwardExtrapolationDuration;
if (defined(forwardExtrapolationDuration)) {
property.forwardExtrapolationDuration = forwardExtrapolationDuration;
}
var backwardExtrapolationType = packetData.backwardExtrapolationType;
if (defined(backwardExtrapolationType)) {
property.backwardExtrapolationType = ExtrapolationType[backwardExtrapolationType];
}
var backwardExtrapolationDuration = packetData.backwardExtrapolationDuration;
if (defined(backwardExtrapolationDuration)) {
property.backwardExtrapolationDuration = backwardExtrapolationDuration;
}
}
function processProperty(type, object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection) {
var combinedInterval;
var packetInterval = packetData.interval;
if (defined(packetInterval)) {
iso8601Scratch.iso8601 = packetInterval;
combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
if (defined(constrainedInterval)) {
combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
}
} else if (defined(constrainedInterval)) {
combinedInterval = constrainedInterval;
}
var packedLength;
var isSampled;
var unwrappedInterval;
var unwrappedIntervalLength;
var isReference = defined(packetData.reference);
var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL);
if (!isReference) {
unwrappedInterval = unwrapInterval(type, packetData, sourceUri);
packedLength = defaultValue(type.packedLength, 1);
unwrappedIntervalLength = defaultValue(unwrappedInterval.length, 1);
isSampled = !defined(packetData.array) && (typeof unwrappedInterval !== 'string') && unwrappedIntervalLength > packedLength;
}
//Rotation is a special case because it represents a native type (Number)
//and therefore does not need to be unpacked when loaded as a constant value.
var needsUnpacking = typeof type.unpack === 'function' && type !== Rotation;
//Any time a constant value is assigned, it completely blows away anything else.
if (!isSampled && !hasInterval) {
if (isReference) {
object[propertyName] = makeReference(entityCollection, packetData.reference);
} else if (needsUnpacking) {
object[propertyName] = new ConstantProperty(type.unpack(unwrappedInterval, 0));
} else {
object[propertyName] = new ConstantProperty(unwrappedInterval);
}
return;
}
var property = object[propertyName];
var epoch;
var packetEpoch = packetData.epoch;
if (defined(packetEpoch)) {
epoch = JulianDate.fromIso8601(packetEpoch);
}
//Without an interval, any sampled value is infinite, meaning it completely
//replaces any non-sampled property that may exist.
if (isSampled && !hasInterval) {
if (!(property instanceof SampledProperty)) {
property = new SampledProperty(type);
object[propertyName] = property;
}
property.addSamplesPackedArray(unwrappedInterval, epoch);
updateInterpolationSettings(packetData, property);
return;
}
var interval;
//A constant value with an interval is normally part of a TimeIntervalCollection,
//However, if the current property is not a time-interval collection, we need
//to turn it into a Composite, preserving the old data with the new interval.
if (!isSampled && hasInterval) {
//Create a new interval for the constant value.
combinedInterval = combinedInterval.clone();
if (isReference) {
combinedInterval.data = makeReference(entityCollection, packetData.reference);
} else if (needsUnpacking) {
combinedInterval.data = type.unpack(unwrappedInterval, 0);
} else {
combinedInterval.data = unwrappedInterval;
}
//If no property exists, simply use a new interval collection
if (!defined(property)) {
if (isReference) {
property = new CompositeProperty();
} else {
property = new TimeIntervalCollectionProperty();
}
object[propertyName] = property;
}
if (!isReference && property instanceof TimeIntervalCollectionProperty) {
//If we create a collection, or it already existed, use it.
property.intervals.addInterval(combinedInterval);
} else if (property instanceof CompositeProperty) {
//If the collection was already a CompositeProperty, use it.
combinedInterval.data = isReference ? combinedInterval.data : new ConstantProperty(combinedInterval.data);
property.intervals.addInterval(combinedInterval);
} else {
//Otherwise, create a CompositeProperty but preserve the existing data.
//Put the old property in an infinite interval.
interval = Iso8601.MAXIMUM_INTERVAL.clone();
interval.data = property;
//Create the composite.
property = new CompositeProperty();
object[propertyName] = property;
//add the old property interval
property.intervals.addInterval(interval);
//Change the new data to a ConstantProperty and add it.
combinedInterval.data = isReference ? combinedInterval.data : new ConstantProperty(combinedInterval.data);
property.intervals.addInterval(combinedInterval);
}
return;
}
//isSampled && hasInterval
if (!defined(property)) {
property = new CompositeProperty();
object[propertyName] = property;
}
//create a CompositeProperty but preserve the existing data.
if (!(property instanceof CompositeProperty)) {
//Put the old property in an infinite interval.
interval = Iso8601.MAXIMUM_INTERVAL.clone();
interval.data = property;
//Create the composite.
property = new CompositeProperty();
object[propertyName] = property;
//add the old property interval
property.intervals.addInterval(interval);
}
//Check if the interval already exists in the composite
var intervals = property.intervals;
interval = intervals.findInterval(combinedInterval);
if (!defined(interval) || !(interval.data instanceof SampledProperty)) {
//If not, create a SampledProperty for it.
interval = combinedInterval.clone();
interval.data = new SampledProperty(type);
intervals.addInterval(interval);
}
interval.data.addSamplesPackedArray(unwrappedInterval, epoch);
updateInterpolationSettings(packetData, interval.data);
}
function processPacketData(type, object, propertyName, packetData, interval, sourceUri, entityCollection) {
if (!defined(packetData)) {
return;
}
if (isArray(packetData)) {
for (var i = 0, len = packetData.length; i < len; i++) {
processProperty(type, object, propertyName, packetData[i], interval, sourceUri, entityCollection);
}
} else {
processProperty(type, object, propertyName, packetData, interval, sourceUri, entityCollection);
}
}
function processPositionProperty(object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection) {
var combinedInterval;
var packetInterval = packetData.interval;
if (defined(packetInterval)) {
iso8601Scratch.iso8601 = packetInterval;
combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
if (defined(constrainedInterval)) {
combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
}
} else if (defined(constrainedInterval)) {
combinedInterval = constrainedInterval;
}
var referenceFrame;
var unwrappedInterval;
var isSampled = false;
var unwrappedIntervalLength;
var numberOfDerivatives = defined(packetData.cartesianVelocity) ? 1 : 0;
var packedLength = Cartesian3.packedLength * (numberOfDerivatives + 1);
var isReference = defined(packetData.reference);
var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL);
if (!isReference) {
if (defined(packetData.referenceFrame)) {
referenceFrame = ReferenceFrame[packetData.referenceFrame];
}
referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
unwrappedInterval = unwrapCartesianInterval(packetData);
unwrappedIntervalLength = defaultValue(unwrappedInterval.length, 1);
isSampled = unwrappedIntervalLength > packedLength;
}
//Any time a constant value is assigned, it completely blows away anything else.
if (!isSampled && !hasInterval) {
if (isReference) {
object[propertyName] = makeReference(entityCollection, packetData.reference);
} else {
object[propertyName] = new ConstantPositionProperty(Cartesian3.unpack(unwrappedInterval), referenceFrame);
}
return;
}
var property = object[propertyName];
var epoch;
var packetEpoch = packetData.epoch;
if (defined(packetEpoch)) {
epoch = JulianDate.fromIso8601(packetEpoch);
}
//Without an interval, any sampled value is infinite, meaning it completely
//replaces any non-sampled property that may exist.
if (isSampled && !hasInterval) {
if (!(property instanceof SampledPositionProperty) || (defined(referenceFrame) && property.referenceFrame !== referenceFrame)) {
property = new SampledPositionProperty(referenceFrame, numberOfDerivatives);
object[propertyName] = property;
}
property.addSamplesPackedArray(unwrappedInterval, epoch);
updateInterpolationSettings(packetData, property);
return;
}
var interval;
//A constant value with an interval is normally part of a TimeIntervalCollection,
//However, if the current property is not a time-interval collection, we need
//to turn it into a Composite, preserving the old data with the new interval.
if (!isSampled && hasInterval) {
//Create a new interval for the constant value.
combinedInterval = combinedInterval.clone();
if (isReference) {
combinedInterval.data = makeReference(entityCollection, packetData.reference);
} else {
combinedInterval.data = Cartesian3.unpack(unwrappedInterval);
}
//If no property exists, simply use a new interval collection
if (!defined(property)) {
if (isReference) {
property = new CompositePositionProperty(referenceFrame);
} else {
property = new TimeIntervalCollectionPositionProperty(referenceFrame);
}
object[propertyName] = property;
}
if (!isReference && property instanceof TimeIntervalCollectionPositionProperty && (defined(referenceFrame) && property.referenceFrame === referenceFrame)) {
//If we create a collection, or it already existed, use it.
property.intervals.addInterval(combinedInterval);
} else if (property instanceof CompositePositionProperty) {
//If the collection was already a CompositePositionProperty, use it.
combinedInterval.data = isReference ? combinedInterval.data : new ConstantPositionProperty(combinedInterval.data, referenceFrame);
property.intervals.addInterval(combinedInterval);
} else {
//Otherwise, create a CompositePositionProperty but preserve the existing data.
//Put the old property in an infinite interval.
interval = Iso8601.MAXIMUM_INTERVAL.clone();
interval.data = property;
//Create the composite.
property = new CompositePositionProperty(property.referenceFrame);
object[propertyName] = property;
//add the old property interval
property.intervals.addInterval(interval);
//Change the new data to a ConstantPositionProperty and add it.
combinedInterval.data = isReference ? combinedInterval.data : new ConstantPositionProperty(combinedInterval.data, referenceFrame);
property.intervals.addInterval(combinedInterval);
}
return;
}
//isSampled && hasInterval
if (!defined(property)) {
property = new CompositePositionProperty(referenceFrame);
object[propertyName] = property;
} else if (!(property instanceof CompositePositionProperty)) {
//create a CompositeProperty but preserve the existing data.
//Put the old property in an infinite interval.
interval = Iso8601.MAXIMUM_INTERVAL.clone();
interval.data = property;
//Create the composite.
property = new CompositePositionProperty(property.referenceFrame);
object[propertyName] = property;
//add the old property interval
property.intervals.addInterval(interval);
}
//Check if the interval already exists in the composite
var intervals = property.intervals;
interval = intervals.findInterval(combinedInterval);
if (!defined(interval) || !(interval.data instanceof SampledPositionProperty) || (defined(referenceFrame) && interval.data.referenceFrame !== referenceFrame)) {
//If not, create a SampledPositionProperty for it.
interval = combinedInterval.clone();
interval.data = new SampledPositionProperty(referenceFrame, numberOfDerivatives);
intervals.addInterval(interval);
}
interval.data.addSamplesPackedArray(unwrappedInterval, epoch);
updateInterpolationSettings(packetData, interval.data);
}
function processPositionPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection) {
if (!defined(packetData)) {
return;
}
if (isArray(packetData)) {
for (var i = 0, len = packetData.length; i < len; i++) {
processPositionProperty(object, propertyName, packetData[i], interval, sourceUri, entityCollection);
}
} else {
processPositionProperty(object, propertyName, packetData, interval, sourceUri, entityCollection);
}
}
function processMaterialProperty(object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection) {
var combinedInterval;
var packetInterval = packetData.interval;
if (defined(packetInterval)) {
iso8601Scratch.iso8601 = packetInterval;
combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
if (defined(constrainedInterval)) {
combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
}
} else if (defined(constrainedInterval)) {
combinedInterval = constrainedInterval;
}
var property = object[propertyName];
var existingMaterial;
var existingInterval;
if (defined(combinedInterval)) {
if (!(property instanceof CompositeMaterialProperty)) {
property = new CompositeMaterialProperty();
object[propertyName] = property;
}
//See if we already have data at that interval.
var thisIntervals = property.intervals;
existingInterval = thisIntervals.findInterval({
start : combinedInterval.start,
stop : combinedInterval.stop
});
if (defined(existingInterval)) {
//We have an interval, but we need to make sure the
//new data is the same type of material as the old data.
existingMaterial = existingInterval.data;
} else {
//If not, create it.
existingInterval = combinedInterval.clone();
thisIntervals.addInterval(existingInterval);
}
} else {
existingMaterial = property;
}
var materialData;
if (defined(packetData.solidColor)) {
if (!(existingMaterial instanceof ColorMaterialProperty)) {
existingMaterial = new ColorMaterialProperty();
}
materialData = packetData.solidColor;
processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection);
} else if (defined(packetData.grid)) {
if (!(existingMaterial instanceof GridMaterialProperty)) {
existingMaterial = new GridMaterialProperty();
}
materialData = packetData.grid;
processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
processPacketData(Number, existingMaterial, 'cellAlpha', materialData.cellAlpha, undefined, sourceUri, entityCollection);
processPacketData(Cartesian2, existingMaterial, 'lineCount', materialData.lineCount, undefined, sourceUri, entityCollection);
processPacketData(Cartesian2, existingMaterial, 'lineThickness', materialData.lineThickness, undefined, sourceUri, entityCollection);
processPacketData(Cartesian2, existingMaterial, 'lineOffset', materialData.lineOffset, undefined, sourceUri, entityCollection);
} else if (defined(packetData.image)) {
if (!(existingMaterial instanceof ImageMaterialProperty)) {
existingMaterial = new ImageMaterialProperty();
}
materialData = packetData.image;
processPacketData(Image, existingMaterial, 'image', materialData.image, undefined, sourceUri, entityCollection);
processPacketData(Cartesian2, existingMaterial, 'repeat', materialData.repeat, undefined, sourceUri, entityCollection);
processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
processPacketData(Boolean, existingMaterial, 'transparent', materialData.transparent, undefined, sourceUri, entityCollection);
} else if (defined(packetData.stripe)) {
if (!(existingMaterial instanceof StripeMaterialProperty)) {
existingMaterial = new StripeMaterialProperty();
}
materialData = packetData.stripe;
processPacketData(StripeOrientation, existingMaterial, 'orientation', materialData.orientation, undefined, sourceUri, entityCollection);
processPacketData(Color, existingMaterial, 'evenColor', materialData.evenColor, undefined, sourceUri, entityCollection);
processPacketData(Color, existingMaterial, 'oddColor', materialData.oddColor, undefined, sourceUri, entityCollection);
processPacketData(Number, existingMaterial, 'offset', materialData.offset, undefined, sourceUri, entityCollection);
processPacketData(Number, existingMaterial, 'repeat', materialData.repeat, undefined, sourceUri, entityCollection);
} else if (defined(packetData.polylineOutline)) {
if (!(existingMaterial instanceof PolylineOutlineMaterialProperty)) {
existingMaterial = new PolylineOutlineMaterialProperty();
}
materialData = packetData.polylineOutline;
processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
processPacketData(Color, existingMaterial, 'outlineColor', materialData.outlineColor, undefined, sourceUri, entityCollection);
processPacketData(Number, existingMaterial, 'outlineWidth', materialData.outlineWidth, undefined, sourceUri, entityCollection);
} else if (defined(packetData.polylineGlow)) {
if (!(existingMaterial instanceof PolylineGlowMaterialProperty)) {
existingMaterial = new PolylineGlowMaterialProperty();
}
materialData = packetData.polylineGlow;
processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
processPacketData(Number, existingMaterial, 'glowPower', materialData.glowPower, undefined, sourceUri, entityCollection);
} else if (defined(packetData.polylineArrow)) {
if (!(existingMaterial instanceof PolylineArrowMaterialProperty)) {
existingMaterial = new PolylineArrowMaterialProperty();
}
materialData = packetData.polylineArrow;
processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection);
}
if (defined(existingInterval)) {
existingInterval.data = existingMaterial;
} else {
object[propertyName] = existingMaterial;
}
}
function processMaterialPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection) {
if (!defined(packetData)) {
return;
}
if (isArray(packetData)) {
for (var i = 0, len = packetData.length; i < len; i++) {
processMaterialProperty(object, propertyName, packetData[i], interval, sourceUri, entityCollection);
}
} else {
processMaterialProperty(object, propertyName, packetData, interval, sourceUri, entityCollection);
}
}
function processName(entity, packet, entityCollection, sourceUri) {
entity.name = defaultValue(packet.name, entity.name);
}
function processDescription(entity, packet, entityCollection, sourceUri) {
var descriptionData = packet.description;
if (defined(descriptionData)) {
processPacketData(String, entity, 'description', descriptionData, undefined, sourceUri, entityCollection);
}
}
function processPosition(entity, packet, entityCollection, sourceUri) {
var positionData = packet.position;
if (defined(positionData)) {
processPositionPacketData(entity, 'position', positionData, undefined, sourceUri, entityCollection);
}
}
function processViewFrom(entity, packet, entityCollection, sourceUri) {
var viewFromData = packet.viewFrom;
if (defined(viewFromData)) {
processPacketData(Cartesian3, entity, 'viewFrom', viewFromData, undefined, sourceUri, entityCollection);
}
}
function processOrientation(entity, packet, entityCollection, sourceUri) {
var orientationData = packet.orientation;
if (defined(orientationData)) {
processPacketData(Quaternion, entity, 'orientation', orientationData, undefined, sourceUri, entityCollection);
}
}
function processArrayPacketData(object, propertyName, packetData, entityCollection) {
var references = packetData.references;
if (defined(references)) {
var properties = references.map(function(reference) {
return makeReference(entityCollection, reference);
});
var iso8601Interval = packetData.interval;
if (defined(iso8601Interval)) {
iso8601Interval = TimeInterval.fromIso8601(iso8601Interval);
if (!(object[propertyName] instanceof CompositePositionProperty)) {
iso8601Interval.data = new PropertyArray(properties);
var property = new CompositeProperty();
property.intervals.addInterval(iso8601Interval);
object[propertyName] = property;
}
} else {
object[propertyName] = new PropertyArray(properties);
}
} else {
processPacketData(Array, object, propertyName, packetData, undefined, undefined, entityCollection);
}
}
function processArray(object, propertyName, packetData, entityCollection) {
if (!defined(packetData)) {
return;
}
if (isArray(packetData)) {
for (var i = 0, length = packetData.length; i < length; ++i) {
processArrayPacketData(object, propertyName, packetData[i], entityCollection);
}
} else {
processArrayPacketData(object, propertyName, packetData, entityCollection);
}
}
function processPositionsPacketData(object, propertyName, positionsData, entityCollection) {
if (defined(positionsData.references)) {
var properties = positionsData.references.map(function(reference) {
return makeReference(entityCollection, reference);
});
var iso8601Interval = positionsData.interval;
if (defined(iso8601Interval)) {
iso8601Interval = TimeInterval.fromIso8601(iso8601Interval);
if (!(object[propertyName] instanceof CompositePositionProperty)) {
iso8601Interval.data = new PositionPropertyArray(properties);
var property = new CompositePositionProperty();
property.intervals.addInterval(iso8601Interval);
object[propertyName] = property;
}
} else {
object[propertyName] = new PositionPropertyArray(properties);
}
} else {
if (defined(positionsData.cartesian)) {
positionsData.array = Cartesian3.unpackArray(positionsData.cartesian);
} else if (defined(positionsData.cartographicRadians)) {
positionsData.array = Cartesian3.fromRadiansArrayHeights(positionsData.cartographicRadians);
} else if (defined(positionsData.cartographicDegrees)) {
positionsData.array = Cartesian3.fromDegreesArrayHeights(positionsData.cartographicDegrees);
}
if (defined(positionsData.array)) {
processPacketData(Array, object, propertyName, positionsData, undefined, undefined, entityCollection);
}
}
}
function processPositions(object, propertyName, positionsData, entityCollection) {
if (!defined(positionsData)) {
return;
}
if (isArray(positionsData)) {
for (var i = 0, length = positionsData.length; i < length; i++) {
processPositionsPacketData(object, propertyName, positionsData[i], entityCollection);
}
} else {
processPositionsPacketData(object, propertyName, positionsData, entityCollection);
}
}
function processAvailability(entity, packet, entityCollection, sourceUri) {
var interval;
var packetData = packet.availability;
if (!defined(packetData)) {
return;
}
var intervals;
if (isArray(packetData)) {
var length = packetData.length;
for (var i = 0; i < length; i++) {
if (!defined(intervals)) {
intervals = new TimeIntervalCollection();
}
iso8601Scratch.iso8601 = packetData[i];
interval = TimeInterval.fromIso8601(iso8601Scratch);
intervals.addInterval(interval);
}
} else {
iso8601Scratch.iso8601 = packetData;
interval = TimeInterval.fromIso8601(iso8601Scratch);
intervals = new TimeIntervalCollection();
intervals.addInterval(interval);
}
entity.availability = intervals;
}
var iso8601Scratch = {
iso8601 : undefined
};
function processAlignedAxis(billboard, packetData, interval, sourceUri, entityCollection) {
if (!defined(packetData)) {
return;
}
if (defined(packetData.velocityReference)) {
billboard.alignedAxis = new VelocityVectorProperty(makeReference(entityCollection, packetData.velocityReference), true);
} else {
processPacketData(Cartesian3, billboard, 'alignedAxis', packetData, interval, sourceUri, entityCollection);
}
}
function processBillboard(entity, packet, entityCollection, sourceUri) {
var billboardData = packet.billboard;
if (!defined(billboardData)) {
return;
}
var interval;
var intervalString = billboardData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var billboard = entity.billboard;
if (!defined(billboard)) {
entity.billboard = billboard = new BillboardGraphics();
}
processPacketData(Boolean, billboard, 'show', billboardData.show, interval, sourceUri, entityCollection);
processPacketData(Image, billboard, 'image', billboardData.image, interval, sourceUri, entityCollection);
processPacketData(Number, billboard, 'scale', billboardData.scale, interval, sourceUri, entityCollection);
processPacketData(Cartesian2, billboard, 'pixelOffset', billboardData.pixelOffset, interval, sourceUri, entityCollection);
processPacketData(Cartesian3, billboard, 'eyeOffset', billboardData.eyeOffset, interval, sourceUri, entityCollection);
processPacketData(HorizontalOrigin, billboard, 'horizontalOrigin', billboardData.horizontalOrigin, interval, sourceUri, entityCollection);
processPacketData(VerticalOrigin, billboard, 'verticalOrigin', billboardData.verticalOrigin, interval, sourceUri, entityCollection);
processPacketData(HeightReference, billboard, 'heightReference', billboardData.heightReference, interval, sourceUri, entityCollection);
processPacketData(Color, billboard, 'color', billboardData.color, interval, sourceUri, entityCollection);
processPacketData(Rotation, billboard, 'rotation', billboardData.rotation, interval, sourceUri, entityCollection);
processAlignedAxis(billboard, billboardData.alignedAxis, interval, sourceUri, entityCollection);
processPacketData(Boolean, billboard, 'sizeInMeters', billboardData.sizeInMeters, interval, sourceUri, entityCollection);
processPacketData(Number, billboard, 'width', billboardData.width, interval, sourceUri, entityCollection);
processPacketData(Number, billboard, 'height', billboardData.height, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, billboard, 'scaleByDistance', billboardData.scaleByDistance, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, billboard, 'translucencyByDistance', billboardData.translucencyByDistance, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, billboard, 'pixelOffsetScaleByDistance', billboardData.pixelOffsetScaleByDistance, interval, sourceUri, entityCollection);
processPacketData(BoundingRectangle, billboard, 'imageSubRegion', billboardData.imageSubRegion, interval, sourceUri, entityCollection);
}
function processBox(entity, packet, entityCollection, sourceUri) {
var boxData = packet.box;
if (!defined(boxData)) {
return;
}
var interval;
var intervalString = boxData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var box = entity.box;
if (!defined(box)) {
entity.box = box = new BoxGraphics();
}
processPacketData(Boolean, box, 'show', boxData.show, interval, sourceUri, entityCollection);
processPacketData(Cartesian3, box, 'dimensions', boxData.dimensions, interval, sourceUri, entityCollection);
processPacketData(Boolean, box, 'fill', boxData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(box, 'material', boxData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, box, 'outline', boxData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, box, 'outlineColor', boxData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, box, 'outlineWidth', boxData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, box, 'shadows', boxData.shadows, interval, sourceUri, entityCollection);
}
function processCorridor(entity, packet, entityCollection, sourceUri) {
var corridorData = packet.corridor;
if (!defined(corridorData)) {
return;
}
var interval;
var intervalString = corridorData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var corridor = entity.corridor;
if (!defined(corridor)) {
entity.corridor = corridor = new CorridorGraphics();
}
processPacketData(Boolean, corridor, 'show', corridorData.show, interval, sourceUri, entityCollection);
processPositions(corridor, 'positions', corridorData.positions, entityCollection);
processPacketData(Number, corridor, 'width', corridorData.width, interval, sourceUri, entityCollection);
processPacketData(Number, corridor, 'height', corridorData.height, interval, sourceUri, entityCollection);
processPacketData(Number, corridor, 'extrudedHeight', corridorData.extrudedHeight, interval, sourceUri, entityCollection);
processPacketData(CornerType, corridor, 'cornerType', corridorData.cornerType, interval, sourceUri, entityCollection);
processPacketData(Number, corridor, 'granularity', corridorData.granularity, interval, sourceUri, entityCollection);
processPacketData(Boolean, corridor, 'fill', corridorData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(corridor, 'material', corridorData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, corridor, 'outline', corridorData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, corridor, 'outlineColor', corridorData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, corridor, 'outlineWidth', corridorData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, corridor, 'shadows', corridorData.shadows, interval, sourceUri, entityCollection);
}
function processCylinder(entity, packet, entityCollection, sourceUri) {
var cylinderData = packet.cylinder;
if (!defined(cylinderData)) {
return;
}
var interval;
var intervalString = cylinderData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var cylinder = entity.cylinder;
if (!defined(cylinder)) {
entity.cylinder = cylinder = new CylinderGraphics();
}
processPacketData(Boolean, cylinder, 'show', cylinderData.show, interval, sourceUri, entityCollection);
processPacketData(Number, cylinder, 'length', cylinderData.length, interval, sourceUri, entityCollection);
processPacketData(Number, cylinder, 'topRadius', cylinderData.topRadius, interval, sourceUri, entityCollection);
processPacketData(Number, cylinder, 'bottomRadius', cylinderData.bottomRadius, interval, sourceUri, entityCollection);
processPacketData(Boolean, cylinder, 'fill', cylinderData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(cylinder, 'material', cylinderData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, cylinder, 'outline', cylinderData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, cylinder, 'outlineColor', cylinderData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, cylinder, 'outlineWidth', cylinderData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(Number, cylinder, 'numberOfVerticalLines', cylinderData.numberOfVerticalLines, interval, sourceUri, entityCollection);
processPacketData(Number, cylinder, 'slices', cylinderData.slices, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, cylinder, 'shadows', cylinderData.shadows, interval, sourceUri, entityCollection);
}
function processDocument(packet, dataSource) {
var version = packet.version;
if (defined(version)) {
if (typeof version === 'string') {
var tokens = version.split('.');
if (tokens.length === 2) {
if (tokens[0] !== '1') {
throw new RuntimeError('Cesium only supports CZML version 1.');
}
dataSource._version = version;
}
}
}
if (!defined(dataSource._version)) {
throw new RuntimeError('CZML version information invalid. It is expected to be a property on the document object in the . version format.');
}
var documentPacket = dataSource._documentPacket;
if (defined(packet.name)) {
documentPacket.name = packet.name;
}
var clockPacket = packet.clock;
if (defined(clockPacket)) {
var clock = documentPacket.clock;
if (!defined(clock)) {
documentPacket.clock = {
interval : clockPacket.interval,
currentTime : clockPacket.currentTime,
range : clockPacket.range,
step : clockPacket.step,
multiplier : clockPacket.multiplier
};
} else {
clock.interval = defaultValue(clockPacket.interval, clock.interval);
clock.currentTime = defaultValue(clockPacket.currentTime, clock.currentTime);
clock.range = defaultValue(clockPacket.range, clock.range);
clock.step = defaultValue(clockPacket.step, clock.step);
clock.multiplier = defaultValue(clockPacket.multiplier, clock.multiplier);
}
}
}
function processEllipse(entity, packet, entityCollection, sourceUri) {
var ellipseData = packet.ellipse;
if (!defined(ellipseData)) {
return;
}
var interval;
var intervalString = ellipseData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var ellipse = entity.ellipse;
if (!defined(ellipse)) {
entity.ellipse = ellipse = new EllipseGraphics();
}
processPacketData(Boolean, ellipse, 'show', ellipseData.show, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'semiMajorAxis', ellipseData.semiMajorAxis, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'semiMinorAxis', ellipseData.semiMinorAxis, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'height', ellipseData.height, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'extrudedHeight', ellipseData.extrudedHeight, interval, sourceUri, entityCollection);
processPacketData(Rotation, ellipse, 'rotation', ellipseData.rotation, interval, sourceUri, entityCollection);
processPacketData(Rotation, ellipse, 'stRotation', ellipseData.stRotation, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'granularity', ellipseData.granularity, interval, sourceUri, entityCollection);
processPacketData(Boolean, ellipse, 'fill', ellipseData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(ellipse, 'material', ellipseData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, ellipse, 'outline', ellipseData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, ellipse, 'outlineColor', ellipseData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'outlineWidth', ellipseData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(Number, ellipse, 'numberOfVerticalLines', ellipseData.numberOfVerticalLines, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, ellipse, 'shadows', ellipseData.shadows, interval, sourceUri, entityCollection);
}
function processEllipsoid(entity, packet, entityCollection, sourceUri) {
var ellipsoidData = packet.ellipsoid;
if (!defined(ellipsoidData)) {
return;
}
var interval;
var intervalString = ellipsoidData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var ellipsoid = entity.ellipsoid;
if (!defined(ellipsoid)) {
entity.ellipsoid = ellipsoid = new EllipsoidGraphics();
}
processPacketData(Boolean, ellipsoid, 'show', ellipsoidData.show, interval, sourceUri, entityCollection);
processPacketData(Cartesian3, ellipsoid, 'radii', ellipsoidData.radii, interval, sourceUri, entityCollection);
processPacketData(Boolean, ellipsoid, 'fill', ellipsoidData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(ellipsoid, 'material', ellipsoidData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, ellipsoid, 'outline', ellipsoidData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, ellipsoid, 'outlineColor', ellipsoidData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, ellipsoid, 'outlineWidth', ellipsoidData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(Number, ellipsoid, 'stackPartitions', ellipsoidData.stackPartitions, interval, sourceUri, entityCollection);
processPacketData(Number, ellipsoid, 'slicePartitions', ellipsoidData.slicePartitions, interval, sourceUri, entityCollection);
processPacketData(Number, ellipsoid, 'subdivisions', ellipsoidData.subdivisions, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, ellipsoid, 'shadows', ellipsoidData.shadows, interval, sourceUri, entityCollection);
}
function processLabel(entity, packet, entityCollection, sourceUri) {
var labelData = packet.label;
if (!defined(labelData)) {
return;
}
var interval;
var intervalString = labelData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var label = entity.label;
if (!defined(label)) {
entity.label = label = new LabelGraphics();
}
processPacketData(Boolean, label, 'show', labelData.show, interval, sourceUri, entityCollection);
processPacketData(String, label, 'text', labelData.text, interval, sourceUri, entityCollection);
processPacketData(String, label, 'font', labelData.font, interval, sourceUri, entityCollection);
processPacketData(LabelStyle, label, 'style', labelData.style, interval, sourceUri, entityCollection);
processPacketData(Number, label, 'scale', labelData.scale, interval, sourceUri, entityCollection);
processPacketData(Boolean, label, 'showBackground', labelData.showBackground, interval, sourceUri, entityCollection);
processPacketData(Color, label, 'backgroundColor', labelData.backgroundColor, interval, sourceUri, entityCollection);
processPacketData(Cartesian2, label, 'backgroundPadding', labelData.backgroundPadding, interval, sourceUri, entityCollection);
processPacketData(Cartesian2, label, 'pixelOffset', labelData.pixelOffset, interval, sourceUri, entityCollection);
processPacketData(Cartesian3, label, 'eyeOffset', labelData.eyeOffset, interval, sourceUri, entityCollection);
processPacketData(HorizontalOrigin, label, 'horizontalOrigin', labelData.horizontalOrigin, interval, sourceUri, entityCollection);
processPacketData(VerticalOrigin, label, 'verticalOrigin', labelData.verticalOrigin, interval, sourceUri, entityCollection);
processPacketData(HeightReference, label, 'heightReference', labelData.heightReference, interval, sourceUri, entityCollection);
processPacketData(Color, label, 'fillColor', labelData.fillColor, interval, sourceUri, entityCollection);
processPacketData(Color, label, 'outlineColor', labelData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, label, 'outlineWidth', labelData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, label, 'translucencyByDistance', labelData.translucencyByDistance, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, label, 'pixelOffsetScaleByDistance', labelData.pixelOffsetScaleByDistance, interval, sourceUri, entityCollection);
}
function processModel(entity, packet, entityCollection, sourceUri) {
var modelData = packet.model;
if (!defined(modelData)) {
return;
}
var interval;
var intervalString = modelData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var model = entity.model;
if (!defined(model)) {
entity.model = model = new ModelGraphics();
}
processPacketData(Boolean, model, 'show', modelData.show, interval, sourceUri, entityCollection);
processPacketData(Uri, model, 'uri', modelData.gltf, interval, sourceUri, entityCollection);
processPacketData(Number, model, 'scale', modelData.scale, interval, sourceUri, entityCollection);
processPacketData(Number, model, 'minimumPixelSize', modelData.minimumPixelSize, interval, sourceUri, entityCollection);
processPacketData(Number, model, 'maximumScale', modelData.maximumScale, interval, sourceUri, entityCollection);
processPacketData(Boolean, model, 'incrementallyLoadTextures', modelData.incrementallyLoadTextures, interval, sourceUri, entityCollection);
processPacketData(Boolean, model, 'runAnimations', modelData.runAnimations, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, model, 'shadows', modelData.shadows, interval, sourceUri, entityCollection);
processPacketData(HeightReference, model, 'heightReference', modelData.heightReference, interval, sourceUri, entityCollection);
processPacketData(Color, model, 'silhouetteColor', modelData.silhouetteColor, interval, sourceUri, entityCollection);
processPacketData(Number, model, 'silhouetteSize', modelData.silhouetteSize, interval, sourceUri, entityCollection);
processPacketData(Color, model, 'color', modelData.color, interval, sourceUri, entityCollection);
processPacketData(ColorBlendMode, model, 'colorBlendMode', modelData.colorBlendMode, interval, sourceUri, entityCollection);
processPacketData(Number, model, 'colorBlendAmount', modelData.colorBlendAmount, interval, sourceUri, entityCollection);
var nodeTransformationsData = modelData.nodeTransformations;
if (defined(nodeTransformationsData)) {
if (isArray(nodeTransformationsData)) {
for (var i = 0, len = nodeTransformationsData.length; i < len; i++) {
processNodeTransformations(model, nodeTransformationsData[i], interval, sourceUri, entityCollection);
}
} else {
processNodeTransformations(model, nodeTransformationsData, interval, sourceUri, entityCollection);
}
}
}
function processNodeTransformations(model, nodeTransformationsData, constrainedInterval, sourceUri, entityCollection) {
var combinedInterval;
var packetInterval = nodeTransformationsData.interval;
if (defined(packetInterval)) {
iso8601Scratch.iso8601 = packetInterval;
combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
if (defined(constrainedInterval)) {
combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
}
} else if (defined(constrainedInterval)) {
combinedInterval = constrainedInterval;
}
var nodeTransformations = model.nodeTransformations;
var nodeNames = Object.keys(nodeTransformationsData);
for (var i = 0, len = nodeNames.length; i < len; ++i) {
var nodeName = nodeNames[i];
if (nodeName === 'interval') {
continue;
}
var nodeTransformationData = nodeTransformationsData[nodeName];
if (!defined(nodeTransformationData)) {
continue;
}
if (!defined(nodeTransformations)) {
model.nodeTransformations = nodeTransformations = new PropertyBag();
}
if (!nodeTransformations.hasProperty(nodeName)) {
nodeTransformations.addProperty(nodeName);
}
var nodeTransformation = nodeTransformations[nodeName];
if (!defined(nodeTransformation)) {
nodeTransformations[nodeName] = nodeTransformation = new NodeTransformationProperty();
}
processPacketData(Cartesian3, nodeTransformation, 'translation', nodeTransformationData.translation, combinedInterval, sourceUri, entityCollection);
processPacketData(Quaternion, nodeTransformation, 'rotation', nodeTransformationData.rotation, combinedInterval, sourceUri, entityCollection);
processPacketData(Cartesian3, nodeTransformation, 'scale', nodeTransformationData.scale, combinedInterval, sourceUri, entityCollection);
}
}
function processPath(entity, packet, entityCollection, sourceUri) {
var pathData = packet.path;
if (!defined(pathData)) {
return;
}
var interval;
var intervalString = pathData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var path = entity.path;
if (!defined(path)) {
entity.path = path = new PathGraphics();
}
processPacketData(Boolean, path, 'show', pathData.show, interval, sourceUri, entityCollection);
processPacketData(Number, path, 'width', pathData.width, interval, sourceUri, entityCollection);
processPacketData(Number, path, 'resolution', pathData.resolution, interval, sourceUri, entityCollection);
processPacketData(Number, path, 'leadTime', pathData.leadTime, interval, sourceUri, entityCollection);
processPacketData(Number, path, 'trailTime', pathData.trailTime, interval, sourceUri, entityCollection);
processMaterialPacketData(path, 'material', pathData.material, interval, sourceUri, entityCollection);
}
function processPoint(entity, packet, entityCollection, sourceUri) {
var pointData = packet.point;
if (!defined(pointData)) {
return;
}
var interval;
var intervalString = pointData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var point = entity.point;
if (!defined(point)) {
entity.point = point = new PointGraphics();
}
processPacketData(Boolean, point, 'show', pointData.show, interval, sourceUri, entityCollection);
processPacketData(Number, point, 'pixelSize', pointData.pixelSize, interval, sourceUri, entityCollection);
processPacketData(HeightReference, point, 'heightReference', pointData.heightReference, interval, sourceUri, entityCollection);
processPacketData(Color, point, 'color', pointData.color, interval, sourceUri, entityCollection);
processPacketData(Color, point, 'outlineColor', pointData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, point, 'outlineWidth', pointData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, point, 'scaleByDistance', pointData.scaleByDistance, interval, sourceUri, entityCollection);
processPacketData(NearFarScalar, point, 'translucencyByDistance', pointData.translucencyByDistance, interval, sourceUri, entityCollection);
}
function processPolygon(entity, packet, entityCollection, sourceUri) {
var polygonData = packet.polygon;
if (!defined(polygonData)) {
return;
}
var interval;
var intervalString = polygonData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var polygon = entity.polygon;
if (!defined(polygon)) {
entity.polygon = polygon = new PolygonGraphics();
}
processPacketData(Boolean, polygon, 'show', polygonData.show, interval, sourceUri, entityCollection);
processPositions(polygon, 'hierarchy', polygonData.positions, entityCollection);
processPacketData(Number, polygon, 'height', polygonData.height, interval, sourceUri, entityCollection);
processPacketData(Number, polygon, 'extrudedHeight', polygonData.extrudedHeight, interval, sourceUri, entityCollection);
processPacketData(Rotation, polygon, 'stRotation', polygonData.stRotation, interval, sourceUri, entityCollection);
processPacketData(Number, polygon, 'granularity', polygonData.granularity, interval, sourceUri, entityCollection);
processPacketData(Boolean, polygon, 'fill', polygonData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(polygon, 'material', polygonData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, polygon, 'outline', polygonData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, polygon, 'outlineColor', polygonData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, polygon, 'outlineWidth', polygonData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(Boolean, polygon, 'perPositionHeight', polygonData.perPositionHeight, interval, sourceUri, entityCollection);
processPacketData(Boolean, polygon, 'closeTop', polygonData.closeTop, interval, sourceUri, entityCollection);
processPacketData(Boolean, polygon, 'closeBottom', polygonData.closeBottom, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, polygon, 'shadows', polygonData.shadows, interval, sourceUri, entityCollection);
}
function processPolyline(entity, packet, entityCollection, sourceUri) {
var polylineData = packet.polyline;
if (!defined(polylineData)) {
return;
}
var interval;
var intervalString = polylineData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var polyline = entity.polyline;
if (!defined(polyline)) {
entity.polyline = polyline = new PolylineGraphics();
}
processPacketData(Boolean, polyline, 'show', polylineData.show, interval, sourceUri, entityCollection);
processPositions(polyline, 'positions', polylineData.positions, entityCollection);
processPacketData(Number, polyline, 'width', polylineData.width, interval, sourceUri, entityCollection);
processPacketData(Number, polyline, 'granularity', polylineData.granularity, interval, sourceUri, entityCollection);
processMaterialPacketData(polyline, 'material', polylineData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, polyline, 'followSurface', polylineData.followSurface, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, polyline, 'shadows', polylineData.shadows, interval, sourceUri, entityCollection);
}
function processRectangle(entity, packet, entityCollection, sourceUri) {
var rectangleData = packet.rectangle;
if (!defined(rectangleData)) {
return;
}
var interval;
var intervalString = rectangleData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var rectangle = entity.rectangle;
if (!defined(rectangle)) {
entity.rectangle = rectangle = new RectangleGraphics();
}
processPacketData(Boolean, rectangle, 'show', rectangleData.show, interval, sourceUri, entityCollection);
processPacketData(Rectangle, rectangle, 'coordinates', rectangleData.coordinates, interval, sourceUri, entityCollection);
processPacketData(Number, rectangle, 'height', rectangleData.height, interval, sourceUri, entityCollection);
processPacketData(Number, rectangle, 'extrudedHeight', rectangleData.extrudedHeight, interval, sourceUri, entityCollection);
processPacketData(Rotation, rectangle, 'rotation', rectangleData.rotation, interval, sourceUri, entityCollection);
processPacketData(Rotation, rectangle, 'stRotation', rectangleData.stRotation, interval, sourceUri, entityCollection);
processPacketData(Number, rectangle, 'granularity', rectangleData.granularity, interval, sourceUri, entityCollection);
processPacketData(Boolean, rectangle, 'fill', rectangleData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(rectangle, 'material', rectangleData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, rectangle, 'outline', rectangleData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, rectangle, 'outlineColor', rectangleData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, rectangle, 'outlineWidth', rectangleData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(Boolean, rectangle, 'closeTop', rectangleData.closeTop, interval, sourceUri, entityCollection);
processPacketData(Boolean, rectangle, 'closeBottom', rectangleData.closeBottom, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, rectangle, 'shadows', rectangleData.shadows, interval, sourceUri, entityCollection);
}
function processWall(entity, packet, entityCollection, sourceUri) {
var wallData = packet.wall;
if (!defined(wallData)) {
return;
}
var interval;
var intervalString = wallData.interval;
if (defined(intervalString)) {
iso8601Scratch.iso8601 = intervalString;
interval = TimeInterval.fromIso8601(iso8601Scratch);
}
var wall = entity.wall;
if (!defined(wall)) {
entity.wall = wall = new WallGraphics();
}
processPacketData(Boolean, wall, 'show', wallData.show, interval, sourceUri, entityCollection);
processPositions(wall, 'positions', wallData.positions, entityCollection);
processArray(wall, 'minimumHeights', wallData.minimumHeights, entityCollection);
processArray(wall, 'maximumHeights', wallData.maximumHeights, entityCollection);
processPacketData(Number, wall, 'granularity', wallData.granularity, interval, sourceUri, entityCollection);
processPacketData(Boolean, wall, 'fill', wallData.fill, interval, sourceUri, entityCollection);
processMaterialPacketData(wall, 'material', wallData.material, interval, sourceUri, entityCollection);
processPacketData(Boolean, wall, 'outline', wallData.outline, interval, sourceUri, entityCollection);
processPacketData(Color, wall, 'outlineColor', wallData.outlineColor, interval, sourceUri, entityCollection);
processPacketData(Number, wall, 'outlineWidth', wallData.outlineWidth, interval, sourceUri, entityCollection);
processPacketData(ShadowMode, wall, 'shadows', wallData.shadows, interval, sourceUri, entityCollection);
}
function processCzmlPacket(packet, entityCollection, updaterFunctions, sourceUri, dataSource) {
var objectId = packet.id;
if (!defined(objectId)) {
objectId = createGuid();
}
currentId = objectId;
if (!defined(dataSource._version) && objectId !== 'document') {
throw new RuntimeError('The first CZML packet is required to be the document object.');
}
if (packet['delete'] === true) {
entityCollection.removeById(objectId);
} else if (objectId === 'document') {
processDocument(packet, dataSource);
} else {
var entity = entityCollection.getOrCreateEntity(objectId);
var parentId = packet.parent;
if (defined(parentId)) {
entity.parent = entityCollection.getOrCreateEntity(parentId);
}
for (var i = updaterFunctions.length - 1; i > -1; i--) {
updaterFunctions[i](entity, packet, entityCollection, sourceUri);
}
}
currentId = undefined;
}
function updateClock(dataSource) {
var clock;
var clockPacket = dataSource._documentPacket.clock;
if (!defined(clockPacket)) {
if (!defined(dataSource._clock)) {
var availability = dataSource._entityCollection.computeAvailability();
if (!availability.start.equals(Iso8601.MINIMUM_VALUE)) {
var startTime = availability.start;
var stopTime = availability.stop;
var totalSeconds = JulianDate.secondsDifference(stopTime, startTime);
var multiplier = Math.round(totalSeconds / 120.0);
clock = new DataSourceClock();
clock.startTime = JulianDate.clone(startTime);
clock.stopTime = JulianDate.clone(stopTime);
clock.clockRange = ClockRange.LOOP_STOP;
clock.multiplier = multiplier;
clock.currentTime = JulianDate.clone(startTime);
clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
dataSource._clock = clock;
return true;
}
}
return false;
}
if (defined(dataSource._clock)) {
clock = dataSource._clock.clone();
} else {
clock = new DataSourceClock();
clock.startTime = Iso8601.MINIMUM_VALUE.clone();
clock.stopTime = Iso8601.MAXIMUM_VALUE.clone();
clock.currentTime = Iso8601.MINIMUM_VALUE.clone();
clock.clockRange = ClockRange.LOOP_STOP;
clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
clock.multiplier = 1.0;
}
if (defined(clockPacket.interval)) {
iso8601Scratch.iso8601 = clockPacket.interval;
var interval = TimeInterval.fromIso8601(iso8601Scratch);
clock.startTime = interval.start;
clock.stopTime = interval.stop;
}
if (defined(clockPacket.currentTime)) {
clock.currentTime = JulianDate.fromIso8601(clockPacket.currentTime);
}
if (defined(clockPacket.range)) {
clock.clockRange = defaultValue(ClockRange[clockPacket.range], ClockRange.LOOP_STOP);
}
if (defined(clockPacket.step)) {
clock.clockStep = defaultValue(ClockStep[clockPacket.step], ClockStep.SYSTEM_CLOCK_MULTIPLIER);
}
if (defined(clockPacket.multiplier)) {
clock.multiplier = clockPacket.multiplier;
}
if (!clock.equals(dataSource._clock)) {
dataSource._clock = clock.clone(dataSource._clock);
return true;
}
return false;
}
function load(dataSource, czml, options, clear) {
if (!defined(czml)) {
throw new DeveloperError('czml is required.');
}
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var promise = czml;
var sourceUri = options.sourceUri;
if (typeof czml === 'string') {
promise = loadJson(czml);
sourceUri = defaultValue(sourceUri, czml);
}
DataSource.setLoading(dataSource, true);
return when(promise, function(czml) {
return loadCzml(dataSource, czml, sourceUri, clear);
}).otherwise(function(error) {
DataSource.setLoading(dataSource, false);
dataSource._error.raiseEvent(dataSource, error);
console.log(error);
return when.reject(error);
});
}
function loadCzml(dataSource, czml, sourceUri, clear) {
DataSource.setLoading(dataSource, true);
var entityCollection = dataSource._entityCollection;
if (clear) {
dataSource._version = undefined;
dataSource._documentPacket = new DocumentPacket();
entityCollection.removeAll();
}
CzmlDataSource._processCzml(czml, entityCollection, sourceUri, undefined, dataSource);
var raiseChangedEvent = updateClock(dataSource);
var documentPacket = dataSource._documentPacket;
if (defined(documentPacket.name) && dataSource._name !== documentPacket.name) {
dataSource._name = documentPacket.name;
raiseChangedEvent = true;
} else if (!defined(dataSource._name) && defined(sourceUri)) {
dataSource._name = getFilenameFromUri(sourceUri);
raiseChangedEvent = true;
}
DataSource.setLoading(dataSource, false);
if (raiseChangedEvent) {
dataSource._changed.raiseEvent(dataSource);
}
return dataSource;
}
function DocumentPacket() {
this.name = undefined;
this.clock = undefined;
}
/**
* A {@link DataSource} which processes {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/CZML-Guide|CZML}.
* @alias CzmlDataSource
* @constructor
*
* @param {String} [name] An optional name for the data source. This value will be overwritten if a loaded document contains a name.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=CZML.html|Cesium Sandcastle CZML Demo}
*/
function CzmlDataSource(name) {
this._name = name;
this._changed = new Event();
this._error = new Event();
this._isLoading = false;
this._loading = new Event();
this._clock = undefined;
this._documentPacket = new DocumentPacket();
this._version = undefined;
this._entityCollection = new EntityCollection(this);
this._entityCluster = new EntityCluster();
}
/**
* Creates a Promise to a new instance loaded with the provided CZML data.
*
* @param {String|Object} data A url or CZML object to be processed.
* @param {Object} [options] An object with the following properties:
* @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
* @returns {Promise.} A promise that resolves to the new instance once the data is processed.
*/
CzmlDataSource.load = function(czml, options) {
return new CzmlDataSource().load(czml, options);
};
defineProperties(CzmlDataSource.prototype, {
/**
* Gets a human-readable name for this instance.
* @memberof CzmlDataSource.prototype
* @type {String}
*/
name : {
get : function() {
return this._name;
}
},
/**
* Gets the clock settings defined by the loaded CZML. If no clock is explicitly
* defined in the CZML, the combined availability of all objects is returned. If
* only static data exists, this value is undefined.
* @memberof CzmlDataSource.prototype
* @type {DataSourceClock}
*/
clock : {
get : function() {
return this._clock;
}
},
/**
* Gets the collection of {@link Entity} instances.
* @memberof CzmlDataSource.prototype
* @type {EntityCollection}
*/
entities : {
get : function() {
return this._entityCollection;
}
},
/**
* Gets a value indicating if the data source is currently loading data.
* @memberof CzmlDataSource.prototype
* @type {Boolean}
*/
isLoading : {
get : function() {
return this._isLoading;
}
},
/**
* Gets an event that will be raised when the underlying data changes.
* @memberof CzmlDataSource.prototype
* @type {Event}
*/
changedEvent : {
get : function() {
return this._changed;
}
},
/**
* Gets an event that will be raised if an error is encountered during processing.
* @memberof CzmlDataSource.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._error;
}
},
/**
* Gets an event that will be raised when the data source either starts or stops loading.
* @memberof CzmlDataSource.prototype
* @type {Event}
*/
loadingEvent : {
get : function() {
return this._loading;
}
},
/**
* Gets whether or not this data source should be displayed.
* @memberof CzmlDataSource.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._entityCollection.show;
},
set : function(value) {
this._entityCollection.show = value;
}
},
/**
* Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
*
* @memberof CzmlDataSource.prototype
* @type {EntityCluster}
*/
clustering : {
get : function() {
return this._entityCluster;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value must be defined.');
}
this._entityCluster = value;
}
}
});
/**
* Gets the array of CZML processing functions.
* @memberof CzmlDataSource
* @type Array
*/
CzmlDataSource.updaters = [
processBillboard, //
processBox, //
processCorridor, //
processCylinder, //
processEllipse, //
processEllipsoid, //
processLabel, //
processModel, //
processName, //
processDescription, //
processPath, //
processPoint, //
processPolygon, //
processPolyline, //
processRectangle, //
processPosition, //
processViewFrom, //
processWall, //
processOrientation, //
processAvailability];
/**
* Processes the provided url or CZML object without clearing any existing data.
*
* @param {String|Object} czml A url or CZML object to be processed.
* @param {Object} [options] An object with the following properties:
* @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
* @returns {Promise.} A promise that resolves to this instances once the data is processed.
*/
CzmlDataSource.prototype.process = function(czml, options) {
return load(this, czml, options, false);
};
/**
* Loads the provided url or CZML object, replacing any existing data.
*
* @param {String|Object} czml A url or CZML object to be processed.
* @param {Object} [options] An object with the following properties:
* @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
* @returns {Promise.} A promise that resolves to this instances once the data is processed.
*/
CzmlDataSource.prototype.load = function(czml, options) {
return load(this, czml, options, true);
};
/**
* A helper function used by custom CZML updater functions
* which creates or updates a {@link Property} from a CZML packet.
* @function
*
* @param {Function} type The constructor function for the property being processed.
* @param {Object} object The object on which the property will be added or updated.
* @param {String} propertyName The name of the property on the object.
* @param {Object} packetData The CZML packet being processed.
* @param {TimeInterval} interval A constraining interval for which the data is valid.
* @param {String} sourceUri The originating uri of the data being processed.
* @param {EntityCollection} entityCollection The collection being processsed.
*/
CzmlDataSource.processPacketData = processPacketData;
/**
* A helper function used by custom CZML updater functions
* which creates or updates a {@link PositionProperty} from a CZML packet.
* @function
*
* @param {Object} object The object on which the property will be added or updated.
* @param {String} propertyName The name of the property on the object.
* @param {Object} packetData The CZML packet being processed.
* @param {TimeInterval} interval A constraining interval for which the data is valid.
* @param {String} sourceUri The originating uri of the data being processed.
* @param {EntityCollection} entityCollection The collection being processsed.
*/
CzmlDataSource.processPositionPacketData = processPositionPacketData;
/**
* A helper function used by custom CZML updater functions
* which creates or updates a {@link MaterialProperty} from a CZML packet.
* @function
*
* @param {Object} object The object on which the property will be added or updated.
* @param {String} propertyName The name of the property on the object.
* @param {Object} packetData The CZML packet being processed.
* @param {TimeInterval} interval A constraining interval for which the data is valid.
* @param {String} sourceUri The originating uri of the data being processed.
* @param {EntityCollection} entityCollection The collection being processsed.
*/
CzmlDataSource.processMaterialPacketData = processMaterialPacketData;
CzmlDataSource._processCzml = function(czml, entityCollection, sourceUri, updaterFunctions, dataSource) {
updaterFunctions = defined(updaterFunctions) ? updaterFunctions : CzmlDataSource.updaters;
if (isArray(czml)) {
for (var i = 0, len = czml.length; i < len; i++) {
processCzmlPacket(czml[i], entityCollection, updaterFunctions, sourceUri, dataSource);
}
} else {
processCzmlPacket(czml, entityCollection, updaterFunctions, sourceUri, dataSource);
}
};
return CzmlDataSource;
});
/*global define*/
define('DataSources/DataSourceCollection',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Event',
'../ThirdParty/when'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Event,
when) {
'use strict';
/**
* A collection of {@link DataSource} instances.
* @alias DataSourceCollection
* @constructor
*/
function DataSourceCollection() {
this._dataSources = [];
this._dataSourceAdded = new Event();
this._dataSourceRemoved = new Event();
}
defineProperties(DataSourceCollection.prototype, {
/**
* Gets the number of data sources in this collection.
* @memberof DataSourceCollection.prototype
* @type {Number}
* @readonly
*/
length : {
get : function() {
return this._dataSources.length;
}
},
/**
* An event that is raised when a data source is added to the collection.
* Event handlers are passed the data source that was added.
* @memberof DataSourceCollection.prototype
* @type {Event}
* @readonly
*/
dataSourceAdded : {
get : function() {
return this._dataSourceAdded;
}
},
/**
* An event that is raised when a data source is removed from the collection.
* Event handlers are passed the data source that was removed.
* @memberof DataSourceCollection.prototype
* @type {Event}
* @readonly
*/
dataSourceRemoved : {
get : function() {
return this._dataSourceRemoved;
}
}
});
/**
* Adds a data source to the collection.
*
* @param {DataSource|Promise.} dataSource A data source or a promise to a data source to add to the collection.
* When passing a promise, the data source will not actually be added
* to the collection until the promise resolves successfully.
* @returns {Promise.} A Promise that resolves once the data source has been added to the collection.
*/
DataSourceCollection.prototype.add = function(dataSource) {
if (!defined(dataSource)) {
throw new DeveloperError('dataSource is required.');
}
var that = this;
var dataSources = this._dataSources;
return when(dataSource, function(value) {
//Only add the data source if removeAll has not been called
//Since it was added.
if (dataSources === that._dataSources) {
that._dataSources.push(value);
that._dataSourceAdded.raiseEvent(that, value);
}
return value;
});
};
/**
* Removes a data source from this collection, if present.
*
* @param {DataSource} dataSource The data source to remove.
* @param {Boolean} [destroy=false] Whether to destroy the data source in addition to removing it.
* @returns {Boolean} true if the data source was in the collection and was removed,
* false if the data source was not in the collection.
*/
DataSourceCollection.prototype.remove = function(dataSource, destroy) {
destroy = defaultValue(destroy, false);
var index = this._dataSources.indexOf(dataSource);
if (index !== -1) {
this._dataSources.splice(index, 1);
this._dataSourceRemoved.raiseEvent(this, dataSource);
if (destroy && typeof dataSource.destroy === 'function') {
dataSource.destroy();
}
return true;
}
return false;
};
/**
* Removes all data sources from this collection.
*
* @param {Boolean} [destroy=false] whether to destroy the data sources in addition to removing them.
*/
DataSourceCollection.prototype.removeAll = function(destroy) {
destroy = defaultValue(destroy, false);
var dataSources = this._dataSources;
for (var i = 0, len = dataSources.length; i < len; ++i) {
var dataSource = dataSources[i];
this._dataSourceRemoved.raiseEvent(this, dataSource);
if (destroy && typeof dataSource.destroy === 'function') {
dataSource.destroy();
}
}
this._dataSources = [];
};
/**
* Checks to see if the collection contains a given data source.
*
* @param {DataSource} dataSource The data source to check for.
* @returns {Boolean} true if the collection contains the data source, false otherwise.
*/
DataSourceCollection.prototype.contains = function(dataSource) {
return this.indexOf(dataSource) !== -1;
};
/**
* Determines the index of a given data source in the collection.
*
* @param {DataSource} dataSource The data source to find the index of.
* @returns {Number} The index of the data source in the collection, or -1 if the data source does not exist in the collection.
*/
DataSourceCollection.prototype.indexOf = function(dataSource) {
return this._dataSources.indexOf(dataSource);
};
/**
* Gets a data source by index from the collection.
*
* @param {Number} index the index to retrieve.
*/
DataSourceCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._dataSources[index];
};
/**
* Returns true if this object was destroyed; otherwise, false.
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true if this object was destroyed; otherwise, false.
*
* @see DataSourceCollection#destroy
*/
DataSourceCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the resources held by all data sources in this collection. Explicitly destroying this
* object allows for deterministic release of WebGL resources, instead of relying on the garbage
* collector. Once this object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* dataSourceCollection = dataSourceCollection && dataSourceCollection.destroy();
*
* @see DataSourceCollection#isDestroyed
*/
DataSourceCollection.prototype.destroy = function() {
this.removeAll(true);
return destroyObject(this);
};
return DataSourceCollection;
});
/*global define*/
define('DataSources/EllipseGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/EllipseGeometry',
'../Core/EllipseOutlineGeometry',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/oneTimeWarning',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/GroundPrimitive',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
EllipseGeometry,
EllipseOutlineGeometry,
Event,
GeometryInstance,
Iso8601,
oneTimeWarning,
ShowGeometryInstanceAttribute,
GroundPrimitive,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.center = undefined;
this.semiMajorAxis = undefined;
this.semiMinorAxis = undefined;
this.rotation = undefined;
this.height = undefined;
this.extrudedHeight = undefined;
this.granularity = undefined;
this.stRotation = undefined;
this.numberOfVerticalLines = undefined;
}
/**
* A {@link GeometryUpdater} for ellipses.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias EllipseGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function EllipseGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(EllipseGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._isClosed = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._onTerrain = false;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'ellipse', entity.ellipse, undefined);
}
defineProperties(EllipseGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof EllipseGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof EllipseGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(EllipseGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if outline visibility varies with simulation time.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
get : function() {
return this._isClosed;
}
},
/**
* Gets a value indicating if the geometry should be drawn on terrain.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
onTerrain : {
get : function() {
return this._onTerrain;
}
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof EllipseGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
EllipseGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
EllipseGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
EllipseGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new EllipseGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
EllipseGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new EllipseOutlineGeometry(this._options),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
EllipseGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
EllipseGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
EllipseGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'ellipse')) {
return;
}
var ellipse = this._entity.ellipse;
if (!defined(ellipse)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = ellipse.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = ellipse.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var position = this._entity.position;
var semiMajorAxis = ellipse.semiMajorAxis;
var semiMinorAxis = ellipse.semiMinorAxis;
var show = ellipse.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(position) || !defined(semiMajorAxis) || !defined(semiMinorAxis))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(ellipse.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(ellipse.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(ellipse.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(ellipse.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(ellipse.distanceDisplayCondition, defaultDistanceDisplayCondition);
var rotation = ellipse.rotation;
var height = ellipse.height;
var extrudedHeight = ellipse.extrudedHeight;
var granularity = ellipse.granularity;
var stRotation = ellipse.stRotation;
var outlineWidth = ellipse.outlineWidth;
var numberOfVerticalLines = ellipse.numberOfVerticalLines;
var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) &&
isColorMaterial && GroundPrimitive.isSupported(this._scene);
if (outlineEnabled && onTerrain) {
oneTimeWarning(oneTimeWarning.geometryOutlines);
outlineEnabled = false;
}
this._fillEnabled = fillEnabled;
this._onTerrain = onTerrain;
this._isClosed = defined(extrudedHeight) || onTerrain;
this._outlineEnabled = outlineEnabled;
if (!position.isConstant || //
!semiMajorAxis.isConstant || //
!semiMinorAxis.isConstant || //
!Property.isConstant(rotation) || //
!Property.isConstant(height) || //
!Property.isConstant(extrudedHeight) || //
!Property.isConstant(granularity) || //
!Property.isConstant(stRotation) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(numberOfVerticalLines) || //
(onTerrain && !Property.isConstant(material))) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.center = position.getValue(Iso8601.MINIMUM_VALUE, options.center);
options.semiMajorAxis = semiMajorAxis.getValue(Iso8601.MINIMUM_VALUE, options.semiMajorAxis);
options.semiMinorAxis = semiMinorAxis.getValue(Iso8601.MINIMUM_VALUE, options.semiMinorAxis);
options.rotation = defined(rotation) ? rotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.stRotation = defined(stRotation) ? stRotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.numberOfVerticalLines = defined(numberOfVerticalLines) ? numberOfVerticalLines.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
EllipseGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
this._primitives = primitives;
this._groundPrimitives = groundPrimitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var geometryUpdater = this._geometryUpdater;
var onTerrain = geometryUpdater._onTerrain;
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._outlinePrimitive = undefined;
}
this._primitive = undefined;
var entity = geometryUpdater._entity;
var ellipse = entity.ellipse;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipse.show, time, true)) {
return;
}
var options = this._options;
var center = Property.getValueOrUndefined(entity.position, time, options.center);
var semiMajorAxis = Property.getValueOrUndefined(ellipse.semiMajorAxis, time);
var semiMinorAxis = Property.getValueOrUndefined(ellipse.semiMinorAxis, time);
if (!defined(center) || !defined(semiMajorAxis) || !defined(semiMinorAxis)) {
return;
}
options.center = center;
options.semiMajorAxis = semiMajorAxis;
options.semiMinorAxis = semiMinorAxis;
options.rotation = Property.getValueOrUndefined(ellipse.rotation, time);
options.height = Property.getValueOrUndefined(ellipse.height, time);
options.extrudedHeight = Property.getValueOrUndefined(ellipse.extrudedHeight, time);
options.granularity = Property.getValueOrUndefined(ellipse.granularity, time);
options.stRotation = Property.getValueOrUndefined(ellipse.stRotation, time);
options.numberOfVerticalLines = Property.getValueOrUndefined(ellipse.numberOfVerticalLines, time);
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (Property.getValueOrDefault(ellipse.fill, time, true)) {
var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
this._material = material;
if (onTerrain) {
var currentColor = Color.WHITE;
if (defined(fillMaterialProperty.color)) {
currentColor = fillMaterialProperty.color.getValue(time);
}
this._primitive = groundPrimitives.add(new GroundPrimitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new EllipseGeometry(options),
attributes: {
color: ColorGeometryInstanceAttribute.fromColor(currentColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
asynchronous : false,
shadows : shadows
}));
} else {
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : defined(options.extrudedHeight)
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new EllipseGeometry(options)
}),
attributes : {
distanceDisplayCondition : distanceDisplayConditionAttribute
},
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
}
if (!onTerrain && Property.getValueOrDefault(ellipse.outline, time, false)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(ellipse.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(ellipse.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new EllipseOutlineGeometry(options),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (this._geometryUpdater._onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
}
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return EllipseGeometryUpdater;
});
/*global define*/
define('DataSources/EllipsoidGeometryUpdater',[
'../Core/Cartesian3',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/EllipsoidGeometry',
'../Core/EllipsoidOutlineGeometry',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/Matrix4',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/SceneMode',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Cartesian3,
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
EllipsoidGeometry,
EllipsoidOutlineGeometry,
Event,
GeometryInstance,
Iso8601,
Matrix4,
ShowGeometryInstanceAttribute,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
SceneMode,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var radiiScratch = new Cartesian3();
var scratchColor = new Color();
var unitSphere = new Cartesian3(1, 1, 1);
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.radii = undefined;
this.stackPartitions = undefined;
this.slicePartitions = undefined;
this.subdivisions = undefined;
}
/**
* A {@link GeometryUpdater} for ellipsoids.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias EllipsoidGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function EllipsoidGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._scene = scene;
this._entity = entity;
this._entitySubscription = entity.definitionChanged.addEventListener(EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'ellipsoid', entity.ellipsoid, undefined);
}
defineProperties(EllipsoidGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof EllipsoidGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof EllipsoidGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(EllipsoidGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if outline visibility varies with simulation time.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
value : true
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof EllipsoidGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
EllipsoidGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
EllipsoidGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
EllipsoidGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new EllipsoidGeometry(this._options),
modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
EllipsoidGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new EllipsoidOutlineGeometry(this._options),
modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
EllipsoidGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
EllipsoidGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'ellipsoid')) {
return;
}
var ellipsoid = entity.ellipsoid;
if (!defined(ellipsoid)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = ellipsoid.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = ellipsoid.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var position = entity.position;
var radii = ellipsoid.radii;
var show = ellipsoid.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(position) || !defined(radii))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(ellipsoid.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(ellipsoid.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(ellipsoid.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(ellipsoid.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(ellipsoid.distanceDisplayCondition, defaultDistanceDisplayCondition);
this._fillEnabled = fillEnabled;
this._outlineEnabled = outlineEnabled;
var stackPartitions = ellipsoid.stackPartitions;
var slicePartitions = ellipsoid.slicePartitions;
var outlineWidth = ellipsoid.outlineWidth;
var subdivisions = ellipsoid.subdivisions;
if (!position.isConstant || //
!Property.isConstant(entity.orientation) || //
!radii.isConstant || //
!Property.isConstant(stackPartitions) || //
!Property.isConstant(slicePartitions) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(subdivisions)) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.radii = radii.getValue(Iso8601.MINIMUM_VALUE, options.radii);
options.stackPartitions = defined(stackPartitions) ? stackPartitions.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.slicePartitions = defined(slicePartitions) ? slicePartitions.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.subdivisions = defined(subdivisions) ? subdivisions.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
EllipsoidGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, geometryUpdater) {
this._entity = geometryUpdater._entity;
this._scene = geometryUpdater._scene;
this._primitives = primitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
this._modelMatrix = new Matrix4();
this._material = undefined;
this._attributes = undefined;
this._outlineAttributes = undefined;
this._lastSceneMode = undefined;
this._lastShow = undefined;
this._lastOutlineShow = undefined;
this._lastOutlineWidth = undefined;
this._lastOutlineColor = undefined;
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var entity = this._entity;
var ellipsoid = entity.ellipsoid;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipsoid.show, time, true)) {
if (defined(this._primitive)) {
this._primitive.show = false;
}
if (defined(this._outlinePrimitive)) {
this._outlinePrimitive.show = false;
}
return;
}
var radii = Property.getValueOrUndefined(ellipsoid.radii, time, radiiScratch);
var modelMatrix = entity._getModelMatrix(time, this._modelMatrix);
if (!defined(modelMatrix) || !defined(radii)) {
if (defined(this._primitive)) {
this._primitive.show = false;
}
if (defined(this._outlinePrimitive)) {
this._outlinePrimitive.show = false;
}
return;
}
//Compute attributes and material.
var appearance;
var showFill = Property.getValueOrDefault(ellipsoid.fill, time, true);
var showOutline = Property.getValueOrDefault(ellipsoid.outline, time, false);
var outlineColor = Property.getValueOrClonedDefault(ellipsoid.outlineColor, time, Color.BLACK, scratchColor);
var material = MaterialProperty.getValue(time, defaultValue(ellipsoid.material, defaultMaterial), this._material);
this._material = material;
// Check properties that could trigger a primitive rebuild.
var stackPartitions = Property.getValueOrUndefined(ellipsoid.stackPartitions, time);
var slicePartitions = Property.getValueOrUndefined(ellipsoid.slicePartitions, time);
var subdivisions = Property.getValueOrUndefined(ellipsoid.subdivisions, time);
var outlineWidth = Property.getValueOrDefault(ellipsoid.outlineWidth, time, 1.0);
//In 3D we use a fast path by modifying Primitive.modelMatrix instead of regenerating the primitive every frame.
var sceneMode = this._scene.mode;
var in3D = sceneMode === SceneMode.SCENE3D;
var options = this._options;
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
//We only rebuild the primitive if something other than the radii has changed
//For the radii, we use unit sphere and then deform it with a scale matrix.
var rebuildPrimitives = !in3D || this._lastSceneMode !== sceneMode || !defined(this._primitive) || //
options.stackPartitions !== stackPartitions || options.slicePartitions !== slicePartitions || //
options.subdivisions !== subdivisions || this._lastOutlineWidth !== outlineWidth;
if (rebuildPrimitives) {
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._lastSceneMode = sceneMode;
this._lastOutlineWidth = outlineWidth;
options.stackPartitions = stackPartitions;
options.slicePartitions = slicePartitions;
options.subdivisions = subdivisions;
options.radii = in3D ? unitSphere : radii;
appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : true
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new EllipsoidGeometry(options),
modelMatrix : !in3D ? modelMatrix : undefined,
attributes : {
show : new ShowGeometryInstanceAttribute(showFill),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new EllipsoidOutlineGeometry(options),
modelMatrix : !in3D ? modelMatrix : undefined,
attributes : {
show : new ShowGeometryInstanceAttribute(showOutline),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : distanceDisplayConditionAttribute
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : outlineColor.alpha !== 1.0,
renderState : {
lineWidth : this._geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
this._lastShow = showFill;
this._lastOutlineShow = showOutline;
this._lastOutlineColor = Color.clone(outlineColor, this._lastOutlineColor);
this._lastDistanceDisplayCondition = distanceDisplayCondition;
} else if (this._primitive.ready) {
//Update attributes only.
var primitive = this._primitive;
var outlinePrimitive = this._outlinePrimitive;
primitive.show = true;
outlinePrimitive.show = true;
appearance = primitive.appearance;
appearance.material = material;
var attributes = this._attributes;
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(entity);
this._attributes = attributes;
}
if (showFill !== this._lastShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(showFill, attributes.show);
this._lastShow = showFill;
}
var outlineAttributes = this._outlineAttributes;
if (!defined(outlineAttributes)) {
outlineAttributes = outlinePrimitive.getGeometryInstanceAttributes(entity);
this._outlineAttributes = outlineAttributes;
}
if (showOutline !== this._lastOutlineShow) {
outlineAttributes.show = ShowGeometryInstanceAttribute.toValue(showOutline, outlineAttributes.show);
this._lastOutlineShow = showOutline;
}
if (!Color.equals(outlineColor, this._lastOutlineColor)) {
outlineAttributes.color = ColorGeometryInstanceAttribute.toValue(outlineColor, outlineAttributes.color);
Color.clone(outlineColor, this._lastOutlineColor);
}
if (!DistanceDisplayCondition.equals(distanceDisplayCondition, this._lastDistanceDisplayCondition)) {
attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
outlineAttributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, outlineAttributes.distanceDisplayCondition);
DistanceDisplayCondition.clone(distanceDisplayCondition, this._lastDistanceDisplayCondition);
}
}
if (in3D) {
//Since we are scaling a unit sphere, we can't let any of the values go to zero.
//Instead we clamp them to a small value. To the naked eye, this produces the same results
//that you get passing EllipsoidGeometry a radii with a zero component.
radii.x = Math.max(radii.x, 0.001);
radii.y = Math.max(radii.y, 0.001);
radii.z = Math.max(radii.z, 0.001);
modelMatrix = Matrix4.multiplyByScale(modelMatrix, radii, modelMatrix);
this._primitive.modelMatrix = modelMatrix;
this._outlinePrimitive.modelMatrix = modelMatrix;
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return EllipsoidGeometryUpdater;
});
/*global define*/
define('DataSources/StaticGeometryColorBatch',[
'../Core/AssociativeArray',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defined',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/Primitive',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
Color,
ColorGeometryInstanceAttribute,
defined,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
ShowGeometryInstanceAttribute,
Primitive,
BoundingSphereState,
Property) {
'use strict';
var colorScratch = new Color();
var distanceDisplayConditionScratch = new DistanceDisplayCondition();
function Batch(primitives, translucent, appearanceType, closed, shadows) {
this.translucent = translucent;
this.appearanceType = appearanceType;
this.closed = closed;
this.shadows = shadows;
this.primitives = primitives;
this.createPrimitive = false;
this.waitingOnCreate = false;
this.primitive = undefined;
this.oldPrimitive = undefined;
this.geometry = new AssociativeArray();
this.updaters = new AssociativeArray();
this.updatersWithAttributes = new AssociativeArray();
this.attributes = new AssociativeArray();
this.subscriptions = new AssociativeArray();
this.showsUpdated = new AssociativeArray();
this.itemsToRemove = [];
}
Batch.prototype.add = function(updater, instance) {
var id = updater.entity.id;
this.createPrimitive = true;
this.geometry.set(id, instance);
this.updaters.set(id, updater);
if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
this.updatersWithAttributes.set(id, updater);
} else {
var that = this;
this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
if (propertyName === 'isShowing') {
that.showsUpdated.set(entity.id, updater);
}
}));
}
};
Batch.prototype.remove = function(updater) {
var id = updater.entity.id;
this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
if (this.updaters.remove(id)) {
this.updatersWithAttributes.remove(id);
var unsubscribe = this.subscriptions.get(id);
if (defined(unsubscribe)) {
unsubscribe();
this.subscriptions.remove(id);
}
}
};
Batch.prototype.update = function(time) {
var isUpdated = true;
var removedCount = 0;
var primitive = this.primitive;
var primitives = this.primitives;
var attributes;
var i;
if (this.createPrimitive) {
var geometries = this.geometry.values;
var geometriesLength = geometries.length;
if (geometriesLength > 0) {
if (defined(primitive)) {
if (!defined(this.oldPrimitive)) {
this.oldPrimitive = primitive;
} else {
primitives.remove(primitive);
}
}
for (i = 0; i < geometriesLength; i++) {
var geometryItem = geometries[i];
var originalAttributes = geometryItem.attributes;
attributes = this.attributes.get(geometryItem.id.id);
if (defined(attributes)) {
if (defined(originalAttributes.show)) {
originalAttributes.show.value = attributes.show;
}
if (defined(originalAttributes.color)) {
originalAttributes.color.value = attributes.color;
}
}
}
primitive = new Primitive({
asynchronous : true,
geometryInstances : geometries,
appearance : new this.appearanceType({
translucent : this.translucent,
closed : this.closed
}),
shadows : this.shadows
});
primitives.add(primitive);
isUpdated = false;
} else {
if (defined(primitive)) {
primitives.remove(primitive);
primitive = undefined;
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
}
this.attributes.removeAll();
this.primitive = primitive;
this.createPrimitive = false;
this.waitingOnCreate = true;
} else if (defined(primitive) && primitive.ready) {
if (defined(this.oldPrimitive)) {
primitives.remove(this.oldPrimitive);
this.oldPrimitive = undefined;
}
var updatersWithAttributes = this.updatersWithAttributes.values;
var length = updatersWithAttributes.length;
var waitingOnCreate = this.waitingOnCreate;
for (i = 0; i < length; i++) {
var updater = updatersWithAttributes[i];
var instance = this.geometry.get(updater.entity.id);
attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) {
var colorProperty = updater.fillMaterialProperty.color;
colorProperty.getValue(time, colorScratch);
if (!Color.equals(attributes._lastColor, colorScratch)) {
attributes._lastColor = Color.clone(colorScratch, attributes._lastColor);
attributes.color = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.color);
if ((this.translucent && attributes.color[3] === 255) || (!this.translucent && attributes.color[3] !== 255)) {
this.itemsToRemove[removedCount++] = updater;
}
}
}
var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time));
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
if (!Property.isConstant(distanceDisplayConditionProperty)) {
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
}
}
}
this.updateShows(primitive);
this.waitingOnCreate = false;
} else if (defined(primitive) && !primitive.ready) {
isUpdated = false;
}
this.itemsToRemove.length = removedCount;
return isUpdated;
};
Batch.prototype.updateShows = function(primitive) {
var showsUpdated = this.showsUpdated.values;
var length = showsUpdated.length;
for (var i = 0; i < length; i++) {
var updater = showsUpdated[i];
var instance = this.geometry.get(updater.entity.id);
var attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
var show = updater.entity.isShowing;
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
}
this.showsUpdated.removeAll();
};
Batch.prototype.contains = function(entity) {
return this.updaters.contains(entity.id);
};
Batch.prototype.getBoundingSphere = function(entity, result) {
var primitive = this.primitive;
if (!primitive.ready) {
return BoundingSphereState.PENDING;
}
var attributes = primitive.getGeometryInstanceAttributes(entity);
if (!defined(attributes) || !defined(attributes.boundingSphere) ||//
(defined(attributes.show) && attributes.show[0] === 0)) {
return BoundingSphereState.FAILED;
}
attributes.boundingSphere.clone(result);
return BoundingSphereState.DONE;
};
Batch.prototype.removeAllPrimitives = function() {
var primitives = this.primitives;
var primitive = this.primitive;
if (defined(primitive)) {
primitives.remove(primitive);
this.primitive = undefined;
this.geometry.removeAll();
this.updaters.removeAll();
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
};
/**
* @private
*/
function StaticGeometryColorBatch(primitives, appearanceType, closed, shadows) {
this._solidBatch = new Batch(primitives, false, appearanceType, closed, shadows);
this._translucentBatch = new Batch(primitives, true, appearanceType, closed, shadows);
}
StaticGeometryColorBatch.prototype.add = function(time, updater) {
var instance = updater.createFillGeometryInstance(time);
if (instance.attributes.color.value[3] === 255) {
this._solidBatch.add(updater, instance);
} else {
this._translucentBatch.add(updater, instance);
}
};
StaticGeometryColorBatch.prototype.remove = function(updater) {
if (!this._solidBatch.remove(updater)) {
this._translucentBatch.remove(updater);
}
};
StaticGeometryColorBatch.prototype.update = function(time) {
var i;
var updater;
//Perform initial update
var isUpdated = this._solidBatch.update(time);
isUpdated = this._translucentBatch.update(time) && isUpdated;
//If any items swapped between solid/translucent, we need to
//move them between batches
var itemsToRemove = this._solidBatch.itemsToRemove;
var solidsToMoveLength = itemsToRemove.length;
if (solidsToMoveLength > 0) {
for (i = 0; i < solidsToMoveLength; i++) {
updater = itemsToRemove[i];
this._solidBatch.remove(updater);
this._translucentBatch.add(updater, updater.createFillGeometryInstance(time));
}
}
itemsToRemove = this._translucentBatch.itemsToRemove;
var translucentToMoveLength = itemsToRemove.length;
if (translucentToMoveLength > 0) {
for (i = 0; i < translucentToMoveLength; i++) {
updater = itemsToRemove[i];
this._translucentBatch.remove(updater);
this._solidBatch.add(updater, updater.createFillGeometryInstance(time));
}
}
//If we moved anything around, we need to re-build the primitive
if (solidsToMoveLength > 0 || translucentToMoveLength > 0) {
isUpdated = this._solidBatch.update(time) && isUpdated;
isUpdated = this._translucentBatch.update(time) && isUpdated;
}
return isUpdated;
};
StaticGeometryColorBatch.prototype.getBoundingSphere = function(entity, result) {
if (this._solidBatch.contains(entity)) {
return this._solidBatch.getBoundingSphere(entity, result);
} else if (this._translucentBatch.contains(entity)) {
return this._translucentBatch.getBoundingSphere(entity, result);
}
return BoundingSphereState.FAILED;
};
StaticGeometryColorBatch.prototype.removeAllPrimitives = function() {
this._solidBatch.removeAllPrimitives();
this._translucentBatch.removeAllPrimitives();
};
return StaticGeometryColorBatch;
});
/*global define*/
define('DataSources/StaticGeometryPerMaterialBatch',[
'../Core/AssociativeArray',
'../Core/defined',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/Primitive',
'./BoundingSphereState',
'./MaterialProperty',
'./Property'
], function(
AssociativeArray,
defined,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
ShowGeometryInstanceAttribute,
Primitive,
BoundingSphereState,
MaterialProperty,
Property) {
'use strict';
var distanceDisplayConditionScratch = new DistanceDisplayCondition();
function Batch(primitives, appearanceType, materialProperty, closed, shadows) {
this.primitives = primitives;
this.appearanceType = appearanceType;
this.materialProperty = materialProperty;
this.closed = closed;
this.shadows = shadows;
this.updaters = new AssociativeArray();
this.createPrimitive = true;
this.primitive = undefined;
this.oldPrimitive = undefined;
this.geometry = new AssociativeArray();
this.material = undefined;
this.updatersWithAttributes = new AssociativeArray();
this.attributes = new AssociativeArray();
this.invalidated = false;
this.removeMaterialSubscription = materialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this);
this.subscriptions = new AssociativeArray();
this.showsUpdated = new AssociativeArray();
}
Batch.prototype.onMaterialChanged = function() {
this.invalidated = true;
};
Batch.prototype.isMaterial = function(updater) {
var material = this.materialProperty;
var updaterMaterial = updater.fillMaterialProperty;
if (updaterMaterial === material) {
return true;
}
if (defined(material)) {
return material.equals(updaterMaterial);
}
return false;
};
Batch.prototype.add = function(time, updater) {
var id = updater.entity.id;
this.updaters.set(id, updater);
this.geometry.set(id, updater.createFillGeometryInstance(time));
if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
this.updatersWithAttributes.set(id, updater);
} else {
var that = this;
this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
if (propertyName === 'isShowing') {
that.showsUpdated.set(entity.id, updater);
}
}));
}
this.createPrimitive = true;
};
Batch.prototype.remove = function(updater) {
var id = updater.entity.id;
this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
if (this.updaters.remove(id)) {
this.updatersWithAttributes.remove(id);
var unsubscribe = this.subscriptions.get(id);
if (defined(unsubscribe)) {
unsubscribe();
this.subscriptions.remove(id);
}
}
return this.createPrimitive;
};
Batch.prototype.update = function(time) {
var isUpdated = true;
var primitive = this.primitive;
var primitives = this.primitives;
var geometries = this.geometry.values;
var attributes;
var i;
if (this.createPrimitive) {
var geometriesLength = geometries.length;
if (geometriesLength > 0) {
if (defined(primitive)) {
if (!defined(this.oldPrimitive)) {
this.oldPrimitive = primitive;
} else {
primitives.remove(primitive);
}
}
for (i = 0; i < geometriesLength; i++) {
var geometry = geometries[i];
var originalAttributes = geometry.attributes;
attributes = this.attributes.get(geometry.id.id);
if (defined(attributes)) {
if (defined(originalAttributes.show)) {
originalAttributes.show.value = attributes.show;
}
if (defined(originalAttributes.color)) {
originalAttributes.color.value = attributes.color;
}
}
}
this.material = MaterialProperty.getValue(time, this.materialProperty, this.material);
primitive = new Primitive({
asynchronous : true,
geometryInstances : geometries,
appearance : new this.appearanceType({
material : this.material,
translucent : this.material.isTranslucent(),
closed : this.closed
}),
shadows : this.shadows
});
primitives.add(primitive);
isUpdated = false;
} else {
if (defined(primitive)) {
primitives.remove(primitive);
primitive = undefined;
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
}
this.attributes.removeAll();
this.primitive = primitive;
this.createPrimitive = false;
} else if (defined(primitive) && primitive.ready) {
if (defined(this.oldPrimitive)) {
primitives.remove(this.oldPrimitive);
this.oldPrimitive = undefined;
}
this.material = MaterialProperty.getValue(time, this.materialProperty, this.material);
this.primitive.appearance.material = this.material;
var updatersWithAttributes = this.updatersWithAttributes.values;
var length = updatersWithAttributes.length;
for (i = 0; i < length; i++) {
var updater = updatersWithAttributes[i];
var entity = updater.entity;
var instance = this.geometry.get(entity.id);
attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
var show = entity.isShowing && (updater.hasConstantFill || updater.isFilled(time));
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
if (!Property.isConstant(distanceDisplayConditionProperty)) {
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
}
}
}
this.updateShows(primitive);
} else if (defined(primitive) && !primitive.ready) {
isUpdated = false;
}
return isUpdated;
};
Batch.prototype.updateShows = function(primitive) {
var showsUpdated = this.showsUpdated.values;
var length = showsUpdated.length;
for (var i = 0; i < length; i++) {
var updater = showsUpdated[i];
var entity = updater.entity;
var instance = this.geometry.get(entity.id);
var attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
var show = entity.isShowing;
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
}
this.showsUpdated.removeAll();
};
Batch.prototype.contains = function(entity) {
return this.updaters.contains(entity.id);
};
Batch.prototype.getBoundingSphere = function(entity, result) {
var primitive = this.primitive;
if (!primitive.ready) {
return BoundingSphereState.PENDING;
}
var attributes = primitive.getGeometryInstanceAttributes(entity);
if (!defined(attributes) || !defined(attributes.boundingSphere) ||
(defined(attributes.show) && attributes.show[0] === 0)) {
return BoundingSphereState.FAILED;
}
attributes.boundingSphere.clone(result);
return BoundingSphereState.DONE;
};
Batch.prototype.destroy = function(time) {
var primitive = this.primitive;
var primitives = this.primitives;
if (defined(primitive)) {
primitives.remove(primitive);
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
}
this.removeMaterialSubscription();
};
/**
* @private
*/
function StaticGeometryPerMaterialBatch(primitives, appearanceType, closed, shadows) {
this._items = [];
this._primitives = primitives;
this._appearanceType = appearanceType;
this._closed = closed;
this._shadows = shadows;
}
StaticGeometryPerMaterialBatch.prototype.add = function(time, updater) {
var items = this._items;
var length = items.length;
for (var i = 0; i < length; i++) {
var item = items[i];
if (item.isMaterial(updater)) {
item.add(time, updater);
return;
}
}
var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._closed, this._shadows);
batch.add(time, updater);
items.push(batch);
};
StaticGeometryPerMaterialBatch.prototype.remove = function(updater) {
var items = this._items;
var length = items.length;
for (var i = length - 1; i >= 0; i--) {
var item = items[i];
if (item.remove(updater)) {
if (item.updaters.length === 0) {
items.splice(i, 1);
item.destroy();
}
break;
}
}
};
StaticGeometryPerMaterialBatch.prototype.update = function(time) {
var i;
var items = this._items;
var length = items.length;
for (i = length - 1; i >= 0; i--) {
var item = items[i];
if (item.invalidated) {
items.splice(i, 1);
var updaters = item.updaters.values;
var updatersLength = updaters.length;
for (var h = 0; h < updatersLength; h++) {
this.add(time, updaters[h]);
}
item.destroy();
}
}
var isUpdated = true;
for (i = 0; i < length; i++) {
isUpdated = items[i].update(time) && isUpdated;
}
return isUpdated;
};
StaticGeometryPerMaterialBatch.prototype.getBoundingSphere = function(entity, result) {
var items = this._items;
var length = items.length;
for (var i = 0; i < length; i++) {
var item = items[i];
if(item.contains(entity)){
return item.getBoundingSphere(entity, result);
}
}
return BoundingSphereState.FAILED;
};
StaticGeometryPerMaterialBatch.prototype.removeAllPrimitives = function() {
var items = this._items;
var length = items.length;
for (var i = 0; i < length; i++) {
items[i].destroy();
}
this._items.length = 0;
};
return StaticGeometryPerMaterialBatch;
});
/*global define*/
define('DataSources/StaticGroundGeometryColorBatch',[
'../Core/AssociativeArray',
'../Core/Color',
'../Core/defined',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/GroundPrimitive',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
Color,
defined,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
ShowGeometryInstanceAttribute,
GroundPrimitive,
BoundingSphereState,
Property) {
"use strict";
var colorScratch = new Color();
var distanceDisplayConditionScratch = new DistanceDisplayCondition();
function Batch(primitives, color, key) {
this.primitives = primitives;
this.color = color;
this.key = key;
this.createPrimitive = false;
this.waitingOnCreate = false;
this.primitive = undefined;
this.oldPrimitive = undefined;
this.geometry = new AssociativeArray();
this.updaters = new AssociativeArray();
this.updatersWithAttributes = new AssociativeArray();
this.attributes = new AssociativeArray();
this.subscriptions = new AssociativeArray();
this.showsUpdated = new AssociativeArray();
this.itemsToRemove = [];
this.isDirty = false;
}
Batch.prototype.add = function(updater, instance) {
var id = updater.entity.id;
this.createPrimitive = true;
this.geometry.set(id, instance);
this.updaters.set(id, updater);
if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
this.updatersWithAttributes.set(id, updater);
} else {
var that = this;
this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
if (propertyName === 'isShowing') {
that.showsUpdated.set(entity.id, updater);
}
}));
}
};
Batch.prototype.remove = function(updater) {
var id = updater.entity.id;
this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
if (this.updaters.remove(id)) {
this.updatersWithAttributes.remove(id);
var unsubscribe = this.subscriptions.get(id);
if (defined(unsubscribe)) {
unsubscribe();
this.subscriptions.remove(id);
}
}
};
var scratchArray = new Array(4);
Batch.prototype.update = function(time) {
var isUpdated = true;
var removedCount = 0;
var primitive = this.primitive;
var primitives = this.primitives;
var attributes;
var i;
if (this.createPrimitive) {
var geometries = this.geometry.values;
var geometriesLength = geometries.length;
if (geometriesLength > 0) {
if (defined(primitive)) {
if (!defined(this.oldPrimitive)) {
this.oldPrimitive = primitive;
} else {
primitives.remove(primitive);
}
}
for (i = 0; i < geometriesLength; i++) {
var geometryItem = geometries[i];
var originalAttributes = geometryItem.attributes;
attributes = this.attributes.get(geometryItem.id.id);
if (defined(attributes)) {
if (defined(originalAttributes.show)) {
originalAttributes.show.value = attributes.show;
}
if (defined(originalAttributes.color)) {
originalAttributes.color.value = attributes.color;
}
}
}
primitive = new GroundPrimitive({
asynchronous : true,
geometryInstances : geometries
});
primitives.add(primitive);
isUpdated = false;
} else {
if (defined(primitive)) {
primitives.remove(primitive);
primitive = undefined;
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
}
this.attributes.removeAll();
this.primitive = primitive;
this.createPrimitive = false;
this.waitingOnCreate = true;
} else if (defined(primitive) && primitive.ready) {
if (defined(this.oldPrimitive)) {
primitives.remove(this.oldPrimitive);
this.oldPrimitive = undefined;
}
var updatersWithAttributes = this.updatersWithAttributes.values;
var length = updatersWithAttributes.length;
var waitingOnCreate = this.waitingOnCreate;
for (i = 0; i < length; i++) {
var updater = updatersWithAttributes[i];
var instance = this.geometry.get(updater.entity.id);
attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) {
var colorProperty = updater.fillMaterialProperty.color;
colorProperty.getValue(time, colorScratch);
if (!Color.equals(attributes._lastColor, colorScratch)) {
attributes._lastColor = Color.clone(colorScratch, attributes._lastColor);
var color = this.color;
var newColor = colorScratch.toBytes(scratchArray);
if (color[0] !== newColor[0] || color[1] !== newColor[1] ||
color[2] !== newColor[2] || color[3] !== newColor[3]) {
this.itemsToRemove[removedCount++] = updater;
}
}
}
var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time));
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
if (!Property.isConstant(distanceDisplayConditionProperty)) {
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
}
}
}
this.updateShows(primitive);
this.waitingOnCreate = false;
} else if (defined(primitive) && !primitive.ready) {
isUpdated = false;
}
this.itemsToRemove.length = removedCount;
return isUpdated;
};
Batch.prototype.updateShows = function(primitive) {
var showsUpdated = this.showsUpdated.values;
var length = showsUpdated.length;
for (var i = 0; i < length; i++) {
var updater = showsUpdated[i];
var instance = this.geometry.get(updater.entity.id);
var attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
var show = updater.entity.isShowing;
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
}
this.showsUpdated.removeAll();
};
Batch.prototype.contains = function(entity) {
return this.updaters.contains(entity.id);
};
Batch.prototype.getBoundingSphere = function(entity, result) {
var primitive = this.primitive;
if (!primitive.ready) {
return BoundingSphereState.PENDING;
}
var bs = primitive.getBoundingSphere(entity);
if (!defined(bs)) {
return BoundingSphereState.FAILED;
}
bs.clone(result);
return BoundingSphereState.DONE;
};
Batch.prototype.removeAllPrimitives = function() {
var primitives = this.primitives;
var primitive = this.primitive;
if (defined(primitive)) {
primitives.remove(primitive);
this.primitive = undefined;
this.geometry.removeAll();
this.updaters.removeAll();
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
};
/**
* @private
*/
function StaticGroundGeometryColorBatch(primitives) {
this._batches = new AssociativeArray();
this._primitives = primitives;
}
StaticGroundGeometryColorBatch.prototype.add = function(time, updater) {
var instance = updater.createFillGeometryInstance(time);
var batches = this._batches;
// instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key
var batchKey = new Uint32Array(instance.attributes.color.value.buffer)[0];
var batch;
if (batches.contains(batchKey)) {
batch = batches.get(batchKey);
} else {
batch = new Batch(this._primitives, instance.attributes.color.value, batchKey);
batches.set(batchKey, batch);
}
batch.add(updater, instance);
return batch;
};
StaticGroundGeometryColorBatch.prototype.remove = function(updater) {
var batchesArray = this._batches.values;
var count = batchesArray.length;
for (var i = 0; i < count; ++i) {
if (batchesArray[i].remove(updater)) {
return;
}
}
};
StaticGroundGeometryColorBatch.prototype.update = function(time) {
var i;
var updater;
//Perform initial update
var isUpdated = true;
var batches = this._batches;
var batchesArray = batches.values;
var batchCount = batchesArray.length;
for (i = 0; i < batchCount; ++i) {
isUpdated = batchesArray[i].update(time) && isUpdated;
}
//If any items swapped between batches we need to move them
for (i = 0; i < batchCount; ++i) {
var oldBatch = batchesArray[i];
var itemsToRemove = oldBatch.itemsToRemove;
var itemsToMoveLength = itemsToRemove.length;
for (var j = 0; j < itemsToMoveLength; j++) {
updater = itemsToRemove[j];
oldBatch.remove(updater);
var newBatch = this.add(time, updater);
oldBatch.isDirty = true;
newBatch.isDirty = true;
}
}
//If we moved anything around, we need to re-build the primitive and remove empty batches
var batchesArrayCopy = batchesArray.slice();
var batchesCopyCount = batchesArrayCopy.length;
for (i = 0; i < batchesCopyCount; ++i) {
var batch = batchesArrayCopy[i];
if (batch.isDirty) {
isUpdated = batchesArrayCopy[i].update(time) && isUpdated;
batch.isDirty = false;
}
if (batch.geometry.length === 0) {
batches.remove(batch.key);
}
}
return isUpdated;
};
StaticGroundGeometryColorBatch.prototype.getBoundingSphere = function(entity, result) {
var batchesArray = this._batches.values;
var batchCount = batchesArray.length;
for (var i = 0; i < batchCount; ++i) {
var batch = batchesArray[i];
if (batch.contains(entity)) {
return batch.getBoundingSphere(entity, result);
}
}
return BoundingSphereState.FAILED;
};
StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() {
var batchesArray = this._batches.values;
var batchCount = batchesArray.length;
for (var i = 0; i < batchCount; ++i) {
batchesArray[i].removeAllPrimitives();
}
};
return StaticGroundGeometryColorBatch;
});
/*global define*/
define('DataSources/StaticOutlineGeometryBatch',[
'../Core/AssociativeArray',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defined',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
Color,
ColorGeometryInstanceAttribute,
defined,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
ShowGeometryInstanceAttribute,
PerInstanceColorAppearance,
Primitive,
BoundingSphereState,
Property) {
'use strict';
function Batch(primitives, translucent, width, shadows) {
this.translucent = translucent;
this.width = width;
this.shadows = shadows;
this.primitives = primitives;
this.createPrimitive = false;
this.waitingOnCreate = false;
this.primitive = undefined;
this.oldPrimitive = undefined;
this.geometry = new AssociativeArray();
this.updaters = new AssociativeArray();
this.updatersWithAttributes = new AssociativeArray();
this.attributes = new AssociativeArray();
this.itemsToRemove = [];
this.subscriptions = new AssociativeArray();
this.showsUpdated = new AssociativeArray();
}
Batch.prototype.add = function(updater, instance) {
var id = updater.entity.id;
this.createPrimitive = true;
this.geometry.set(id, instance);
this.updaters.set(id, updater);
if (!updater.hasConstantOutline || !updater.outlineColorProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
this.updatersWithAttributes.set(id, updater);
} else {
var that = this;
this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
if (propertyName === 'isShowing') {
that.showsUpdated.set(entity.id, updater);
}
}));
}
};
Batch.prototype.remove = function(updater) {
var id = updater.entity.id;
this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
if (this.updaters.remove(id)) {
this.updatersWithAttributes.remove(id);
var unsubscribe = this.subscriptions.get(id);
if (defined(unsubscribe)) {
unsubscribe();
this.subscriptions.remove(id);
}
}
};
var colorScratch = new Color();
var distanceDisplayConditionScratch = new DistanceDisplayCondition();
Batch.prototype.update = function(time) {
var isUpdated = true;
var removedCount = 0;
var primitive = this.primitive;
var primitives = this.primitives;
var attributes;
var i;
if (this.createPrimitive) {
var geometries = this.geometry.values;
var geometriesLength = geometries.length;
if (geometriesLength > 0) {
if (defined(primitive)) {
if (!defined(this.oldPrimitive)) {
this.oldPrimitive = primitive;
} else {
primitives.remove(primitive);
}
}
for (i = 0; i < geometriesLength; i++) {
var geometryItem = geometries[i];
var originalAttributes = geometryItem.attributes;
attributes = this.attributes.get(geometryItem.id.id);
if (defined(attributes)) {
if (defined(originalAttributes.show)) {
originalAttributes.show.value = attributes.show;
}
if (defined(originalAttributes.color)) {
originalAttributes.color.value = attributes.color;
}
}
}
primitive = new Primitive({
asynchronous : true,
geometryInstances : geometries,
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : this.translucent,
renderState : {
lineWidth : this.width
}
}),
shadows : this.shadows
});
primitives.add(primitive);
isUpdated = false;
} else {
if (defined(primitive)) {
primitives.remove(primitive);
primitive = undefined;
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
}
this.attributes.removeAll();
this.primitive = primitive;
this.createPrimitive = false;
this.waitingOnCreate = true;
} else if (defined(primitive) && primitive.ready) {
if (defined(this.oldPrimitive)) {
primitives.remove(this.oldPrimitive);
this.oldPrimitive = undefined;
}
var updatersWithAttributes = this.updatersWithAttributes.values;
var length = updatersWithAttributes.length;
var waitingOnCreate = this.waitingOnCreate;
for (i = 0; i < length; i++) {
var updater = updatersWithAttributes[i];
var instance = this.geometry.get(updater.entity.id);
attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
if (!updater.outlineColorProperty.isConstant || waitingOnCreate) {
var outlineColorProperty = updater.outlineColorProperty;
outlineColorProperty.getValue(time, colorScratch);
if (!Color.equals(attributes._lastColor, colorScratch)) {
attributes._lastColor = Color.clone(colorScratch, attributes._lastColor);
attributes.color = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.color);
if ((this.translucent && attributes.color[3] === 255) || (!this.translucent && attributes.color[3] !== 255)) {
this.itemsToRemove[removedCount++] = updater;
}
}
}
var show = updater.entity.isShowing && (updater.hasConstantOutline || updater.isOutlineVisible(time));
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
if (!Property.isConstant(distanceDisplayConditionProperty)) {
var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
}
}
}
this.updateShows(primitive);
this.waitingOnCreate = false;
} else if (defined(primitive) && !primitive.ready) {
isUpdated = false;
}
this.itemsToRemove.length = removedCount;
return isUpdated;
};
Batch.prototype.updateShows = function(primitive) {
var showsUpdated = this.showsUpdated.values;
var length = showsUpdated.length;
for (var i = 0; i < length; i++) {
var updater = showsUpdated[i];
var instance = this.geometry.get(updater.entity.id);
var attributes = this.attributes.get(instance.id.id);
if (!defined(attributes)) {
attributes = primitive.getGeometryInstanceAttributes(instance.id);
this.attributes.set(instance.id.id, attributes);
}
var show = updater.entity.isShowing;
var currentShow = attributes.show[0] === 1;
if (show !== currentShow) {
attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
}
}
this.showsUpdated.removeAll();
};
Batch.prototype.contains = function(entity) {
return this.updaters.contains(entity.id);
};
Batch.prototype.getBoundingSphere = function(entity, result) {
var primitive = this.primitive;
if (!primitive.ready) {
return BoundingSphereState.PENDING;
}
var attributes = primitive.getGeometryInstanceAttributes(entity);
if (!defined(attributes) || !defined(attributes.boundingSphere) ||//
(defined(attributes.show) && attributes.show[0] === 0)) {
return BoundingSphereState.FAILED;
}
attributes.boundingSphere.clone(result);
return BoundingSphereState.DONE;
};
Batch.prototype.removeAllPrimitives = function() {
var primitives = this.primitives;
var primitive = this.primitive;
if (defined(primitive)) {
primitives.remove(primitive);
this.primitive = undefined;
this.geometry.removeAll();
this.updaters.removeAll();
}
var oldPrimitive = this.oldPrimitive;
if (defined(oldPrimitive)) {
primitives.remove(oldPrimitive);
this.oldPrimitive = undefined;
}
};
/**
* @private
*/
function StaticOutlineGeometryBatch(primitives, scene, shadows) {
this._primitives = primitives;
this._scene = scene;
this._shadows = shadows;
this._solidBatches = new AssociativeArray();
this._translucentBatches = new AssociativeArray();
}
StaticOutlineGeometryBatch.prototype.add = function(time, updater) {
var instance = updater.createOutlineGeometryInstance(time);
var width = this._scene.clampLineWidth(updater.outlineWidth);
var batches;
var batch;
if (instance.attributes.color.value[3] === 255) {
batches = this._solidBatches;
batch = batches.get(width);
if (!defined(batch)) {
batch = new Batch(this._primitives, false, width, this._shadows);
batches.set(width, batch);
}
batch.add(updater, instance);
} else {
batches = this._translucentBatches;
batch = batches.get(width);
if (!defined(batch)) {
batch = new Batch(this._primitives, true, width, this._shadows);
batches.set(width, batch);
}
batch.add(updater, instance);
}
};
StaticOutlineGeometryBatch.prototype.remove = function(updater) {
var i;
var solidBatches = this._solidBatches.values;
var solidBatchesLength = solidBatches.length;
for (i = 0; i < solidBatchesLength; i++) {
if (solidBatches[i].remove(updater)) {
return;
}
}
var translucentBatches = this._translucentBatches.values;
var translucentBatchesLength = translucentBatches.length;
for (i = 0; i < translucentBatchesLength; i++) {
if (translucentBatches[i].remove(updater)) {
return;
}
}
};
StaticOutlineGeometryBatch.prototype.update = function(time) {
var i;
var x;
var updater;
var batch;
var solidBatches = this._solidBatches.values;
var solidBatchesLength = solidBatches.length;
var translucentBatches = this._translucentBatches.values;
var translucentBatchesLength = translucentBatches.length;
var itemsToRemove;
var isUpdated = true;
var needUpdate = false;
do {
needUpdate = false;
for (x = 0; x < solidBatchesLength; x++) {
batch = solidBatches[x];
//Perform initial update
isUpdated = batch.update(time);
//If any items swapped between solid/translucent, we need to
//move them between batches
itemsToRemove = batch.itemsToRemove;
var solidsToMoveLength = itemsToRemove.length;
if (solidsToMoveLength > 0) {
needUpdate = true;
for (i = 0; i < solidsToMoveLength; i++) {
updater = itemsToRemove[i];
batch.remove(updater);
this.add(time, updater);
}
}
}
for (x = 0; x < translucentBatchesLength; x++) {
batch = translucentBatches[x];
//Perform initial update
isUpdated = batch.update(time);
//If any items swapped between solid/translucent, we need to
//move them between batches
itemsToRemove = batch.itemsToRemove;
var translucentToMoveLength = itemsToRemove.length;
if (translucentToMoveLength > 0) {
needUpdate = true;
for (i = 0; i < translucentToMoveLength; i++) {
updater = itemsToRemove[i];
batch.remove(updater);
this.add(time, updater);
}
}
}
} while (needUpdate);
return isUpdated;
};
StaticOutlineGeometryBatch.prototype.getBoundingSphere = function(entity, result) {
var i;
var solidBatches = this._solidBatches.values;
var solidBatchesLength = solidBatches.length;
for (i = 0; i < solidBatchesLength; i++) {
var solidBatch = solidBatches[i];
if(solidBatch.contains(entity)){
return solidBatch.getBoundingSphere(entity, result);
}
}
var translucentBatches = this._translucentBatches.values;
var translucentBatchesLength = translucentBatches.length;
for (i = 0; i < translucentBatchesLength; i++) {
var translucentBatch = translucentBatches[i];
if(translucentBatch.contains(entity)){
return translucentBatch.getBoundingSphere(entity, result);
}
}
return BoundingSphereState.FAILED;
};
StaticOutlineGeometryBatch.prototype.removeAllPrimitives = function() {
var i;
var solidBatches = this._solidBatches.values;
var solidBatchesLength = solidBatches.length;
for (i = 0; i < solidBatchesLength; i++) {
solidBatches[i].removeAllPrimitives();
}
var translucentBatches = this._translucentBatches.values;
var translucentBatchesLength = translucentBatches.length;
for (i = 0; i < translucentBatchesLength; i++) {
translucentBatches[i].removeAllPrimitives();
}
};
return StaticOutlineGeometryBatch;
});
/*global define*/
define('DataSources/GeometryVisualizer',[
'../Core/AssociativeArray',
'../Core/BoundingSphere',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Scene/ShadowMode',
'./BoundingSphereState',
'./ColorMaterialProperty',
'./StaticGeometryColorBatch',
'./StaticGeometryPerMaterialBatch',
'./StaticGroundGeometryColorBatch',
'./StaticOutlineGeometryBatch'
], function(
AssociativeArray,
BoundingSphere,
defined,
destroyObject,
DeveloperError,
ShadowMode,
BoundingSphereState,
ColorMaterialProperty,
StaticGeometryColorBatch,
StaticGeometryPerMaterialBatch,
StaticGroundGeometryColorBatch,
StaticOutlineGeometryBatch) {
'use strict';
var emptyArray = [];
function DynamicGeometryBatch(primitives, groundPrimitives) {
this._primitives = primitives;
this._groundPrimitives = groundPrimitives;
this._dynamicUpdaters = new AssociativeArray();
}
DynamicGeometryBatch.prototype.add = function(time, updater) {
this._dynamicUpdaters.set(updater.entity.id, updater.createDynamicUpdater(this._primitives, this._groundPrimitives));
};
DynamicGeometryBatch.prototype.remove = function(updater) {
var id = updater.entity.id;
var dynamicUpdater = this._dynamicUpdaters.get(id);
if (defined(dynamicUpdater)) {
this._dynamicUpdaters.remove(id);
dynamicUpdater.destroy();
}
};
DynamicGeometryBatch.prototype.update = function(time) {
var geometries = this._dynamicUpdaters.values;
for (var i = 0, len = geometries.length; i < len; i++) {
geometries[i].update(time);
}
return true;
};
DynamicGeometryBatch.prototype.removeAllPrimitives = function() {
var geometries = this._dynamicUpdaters.values;
for (var i = 0, len = geometries.length; i < len; i++) {
geometries[i].destroy();
}
this._dynamicUpdaters.removeAll();
};
DynamicGeometryBatch.prototype.getBoundingSphere = function(entity, result) {
var updater = this._dynamicUpdaters.get(entity.id);
if (defined(updater) && defined(updater.getBoundingSphere)) {
return updater.getBoundingSphere(entity, result);
}
return BoundingSphereState.FAILED;
};
function removeUpdater(that, updater) {
//We don't keep track of which batch an updater is in, so just remove it from all of them.
var batches = that._batches;
var length = batches.length;
for (var i = 0; i < length; i++) {
batches[i].remove(updater);
}
}
function insertUpdaterIntoBatch(that, time, updater) {
if (updater.isDynamic) {
that._dynamicBatch.add(time, updater);
return;
}
var shadows;
if (updater.outlineEnabled || updater.fillEnabled) {
shadows = updater.shadowsProperty.getValue(time);
}
if (updater.outlineEnabled) {
that._outlineBatches[shadows].add(time, updater);
}
if (updater.fillEnabled) {
if (updater.onTerrain) {
that._groundColorBatch.add(time, updater);
} else {
if (updater.isClosed) {
if (updater.fillMaterialProperty instanceof ColorMaterialProperty) {
that._closedColorBatches[shadows].add(time, updater);
} else {
that._closedMaterialBatches[shadows].add(time, updater);
}
} else {
if (updater.fillMaterialProperty instanceof ColorMaterialProperty) {
that._openColorBatches[shadows].add(time, updater);
} else {
that._openMaterialBatches[shadows].add(time, updater);
}
}
}
}
}
/**
* A general purpose visualizer for geometry represented by {@link Primitive} instances.
* @alias GeometryVisualizer
* @constructor
*
* @param {GeometryUpdater} type The updater to be used for creating the geometry.
* @param {Scene} scene The scene the primitives will be rendered in.
* @param {EntityCollection} entityCollection The entityCollection to visualize.
*/
function GeometryVisualizer(type, scene, entityCollection) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(entityCollection)) {
throw new DeveloperError('entityCollection is required.');
}
this._type = type;
var primitives = scene.primitives;
var groundPrimitives = scene.groundPrimitives;
this._scene = scene;
this._primitives = primitives;
this._groundPrimitives = groundPrimitives;
this._entityCollection = undefined;
this._addedObjects = new AssociativeArray();
this._removedObjects = new AssociativeArray();
this._changedObjects = new AssociativeArray();
var numberOfShadowModes = ShadowMode.NUMBER_OF_SHADOW_MODES;
this._outlineBatches = new Array(numberOfShadowModes);
this._closedColorBatches = new Array(numberOfShadowModes);
this._closedMaterialBatches = new Array(numberOfShadowModes);
this._openColorBatches = new Array(numberOfShadowModes);
this._openMaterialBatches = new Array(numberOfShadowModes);
for (var i = 0; i < numberOfShadowModes; ++i) {
this._outlineBatches[i] = new StaticOutlineGeometryBatch(primitives, scene, i);
this._closedColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, true, i);
this._closedMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, true, i);
this._openColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, false, i);
this._openMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, false, i);
}
this._groundColorBatch = new StaticGroundGeometryColorBatch(groundPrimitives);
this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives);
this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatch, this._dynamicBatch);
this._subscriptions = new AssociativeArray();
this._updaters = new AssociativeArray();
this._entityCollection = entityCollection;
entityCollection.collectionChanged.addEventListener(GeometryVisualizer.prototype._onCollectionChanged, this);
this._onCollectionChanged(entityCollection, entityCollection.values, emptyArray);
}
/**
* Updates all of the primitives created by this visualizer to match their
* Entity counterpart at the given time.
*
* @param {JulianDate} time The time to update to.
* @returns {Boolean} True if the visualizer successfully updated to the provided time,
* false if the visualizer is waiting for asynchronous primitives to be created.
*/
GeometryVisualizer.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var addedObjects = this._addedObjects;
var added = addedObjects.values;
var removedObjects = this._removedObjects;
var removed = removedObjects.values;
var changedObjects = this._changedObjects;
var changed = changedObjects.values;
var i;
var entity;
var id;
var updater;
for (i = changed.length - 1; i > -1; i--) {
entity = changed[i];
id = entity.id;
updater = this._updaters.get(id);
//If in a single update, an entity gets removed and a new instance
//re-added with the same id, the updater no longer tracks the
//correct entity, we need to both remove the old one and
//add the new one, which is done by pushing the entity
//onto the removed/added lists.
if (updater.entity === entity) {
removeUpdater(this, updater);
insertUpdaterIntoBatch(this, time, updater);
} else {
removed.push(entity);
added.push(entity);
}
}
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
id = entity.id;
updater = this._updaters.get(id);
removeUpdater(this, updater);
updater.destroy();
this._updaters.remove(id);
this._subscriptions.get(id)();
this._subscriptions.remove(id);
}
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
id = entity.id;
updater = new this._type(entity, this._scene);
this._updaters.set(id, updater);
insertUpdaterIntoBatch(this, time, updater);
this._subscriptions.set(id, updater.geometryChanged.addEventListener(GeometryVisualizer._onGeometryChanged, this));
}
addedObjects.removeAll();
removedObjects.removeAll();
changedObjects.removeAll();
var isUpdated = true;
var batches = this._batches;
var length = batches.length;
for (i = 0; i < length; i++) {
isUpdated = batches[i].update(time) && isUpdated;
}
return isUpdated;
};
var getBoundingSphereArrayScratch = [];
var getBoundingSphereBoundingSphereScratch = new BoundingSphere();
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
GeometryVisualizer.prototype.getBoundingSphere = function(entity, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var boundingSpheres = getBoundingSphereArrayScratch;
var tmp = getBoundingSphereBoundingSphereScratch;
var count = 0;
var state = BoundingSphereState.DONE;
var batches = this._batches;
var batchesLength = batches.length;
for (var i = 0; i < batchesLength; i++) {
state = batches[i].getBoundingSphere(entity, tmp);
if (state === BoundingSphereState.PENDING) {
return BoundingSphereState.PENDING;
} else if (state === BoundingSphereState.DONE) {
boundingSpheres[count] = BoundingSphere.clone(tmp, boundingSpheres[count]);
count++;
}
}
if (count === 0) {
return BoundingSphereState.FAILED;
}
boundingSpheres.length = count;
BoundingSphere.fromBoundingSpheres(boundingSpheres, result);
return BoundingSphereState.DONE;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
GeometryVisualizer.prototype.isDestroyed = function() {
return false;
};
/**
* Removes and destroys all primitives created by this instance.
*/
GeometryVisualizer.prototype.destroy = function() {
this._entityCollection.collectionChanged.removeEventListener(GeometryVisualizer.prototype._onCollectionChanged, this);
this._addedObjects.removeAll();
this._removedObjects.removeAll();
var i;
var batches = this._batches;
var length = batches.length;
for (i = 0; i < length; i++) {
batches[i].removeAllPrimitives();
}
var subscriptions = this._subscriptions.values;
length = subscriptions.length;
for (i = 0; i < length; i++) {
subscriptions[i]();
}
this._subscriptions.removeAll();
return destroyObject(this);
};
/**
* @private
*/
GeometryVisualizer._onGeometryChanged = function(updater) {
var removedObjects = this._removedObjects;
var changedObjects = this._changedObjects;
var entity = updater.entity;
var id = entity.id;
if (!defined(removedObjects.get(id)) && !defined(changedObjects.get(id))) {
changedObjects.set(id, entity);
}
};
/**
* @private
*/
GeometryVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed) {
var addedObjects = this._addedObjects;
var removedObjects = this._removedObjects;
var changedObjects = this._changedObjects;
var i;
var id;
var entity;
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
id = entity.id;
if (!addedObjects.remove(id)) {
removedObjects.set(id, entity);
changedObjects.remove(id);
}
}
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
id = entity.id;
if (removedObjects.remove(id)) {
changedObjects.set(id, entity);
} else {
addedObjects.set(id, entity);
}
}
};
return GeometryVisualizer;
});
/*global define*/
define('DataSources/LabelVisualizer',[
'../Core/AssociativeArray',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/NearFarScalar',
'../Scene/HeightReference',
'../Scene/HorizontalOrigin',
'../Scene/LabelStyle',
'../Scene/VerticalOrigin',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
Cartesian2,
Cartesian3,
Color,
defaultValue,
defined,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
NearFarScalar,
HeightReference,
HorizontalOrigin,
LabelStyle,
VerticalOrigin,
BoundingSphereState,
Property) {
'use strict';
var defaultScale = 1.0;
var defaultFont = '30px sans-serif';
var defaultStyle = LabelStyle.FILL;
var defaultFillColor = Color.WHITE;
var defaultOutlineColor = Color.BLACK;
var defaultOutlineWidth = 1.0;
var defaultShowBackground = false;
var defaultBackgroundColor = new Color(0.165, 0.165, 0.165, 0.8);
var defaultBackgroundPadding = new Cartesian2(7, 5);
var defaultPixelOffset = Cartesian2.ZERO;
var defaultEyeOffset = Cartesian3.ZERO;
var defaultHeightReference = HeightReference.NONE;
var defaultHorizontalOrigin = HorizontalOrigin.CENTER;
var defaultVerticalOrigin = VerticalOrigin.CENTER;
var position = new Cartesian3();
var fillColor = new Color();
var outlineColor = new Color();
var backgroundColor = new Color();
var backgroundPadding = new Cartesian2();
var eyeOffset = new Cartesian3();
var pixelOffset = new Cartesian2();
var translucencyByDistance = new NearFarScalar();
var pixelOffsetScaleByDistance = new NearFarScalar();
var distanceDisplayCondition = new DistanceDisplayCondition();
function EntityData(entity) {
this.entity = entity;
this.label = undefined;
this.index = undefined;
}
/**
* A {@link Visualizer} which maps the {@link LabelGraphics} instance
* in {@link Entity#label} to a {@link Label}.
* @alias LabelVisualizer
* @constructor
*
* @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
* @param {EntityCollection} entityCollection The entityCollection to visualize.
*/
function LabelVisualizer(entityCluster, entityCollection) {
if (!defined(entityCluster)) {
throw new DeveloperError('entityCluster is required.');
}
if (!defined(entityCollection)) {
throw new DeveloperError('entityCollection is required.');
}
entityCollection.collectionChanged.addEventListener(LabelVisualizer.prototype._onCollectionChanged, this);
this._cluster = entityCluster;
this._entityCollection = entityCollection;
this._items = new AssociativeArray();
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
}
/**
* Updates the primitives created by this visualizer to match their
* Entity counterpart at the given time.
*
* @param {JulianDate} time The time to update to.
* @returns {Boolean} This function always returns true.
*/
LabelVisualizer.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var items = this._items.values;
var cluster = this._cluster;
for (var i = 0, len = items.length; i < len; i++) {
var item = items[i];
var entity = item.entity;
var labelGraphics = entity._label;
var text;
var label = item.label;
var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(labelGraphics._show, time, true);
if (show) {
position = Property.getValueOrUndefined(entity._position, time, position);
text = Property.getValueOrUndefined(labelGraphics._text, time);
show = defined(position) && defined(text);
}
if (!show) {
//don't bother creating or updating anything else
returnPrimitive(item, entity, cluster);
continue;
}
if (!Property.isConstant(entity._position)) {
cluster._clusterDirty = true;
}
if (!defined(label)) {
label = cluster.getLabel(entity);
label.id = entity;
item.label = label;
}
label.show = true;
label.position = position;
label.text = text;
label.scale = Property.getValueOrDefault(labelGraphics._scale, time, defaultScale);
label.font = Property.getValueOrDefault(labelGraphics._font, time, defaultFont);
label.style = Property.getValueOrDefault(labelGraphics._style, time, defaultStyle);
label.fillColor = Property.getValueOrDefault(labelGraphics._fillColor, time, defaultFillColor, fillColor);
label.outlineColor = Property.getValueOrDefault(labelGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
label.outlineWidth = Property.getValueOrDefault(labelGraphics._outlineWidth, time, defaultOutlineWidth);
label.showBackground = Property.getValueOrDefault(labelGraphics._showBackground, time, defaultShowBackground);
label.backgroundColor = Property.getValueOrDefault(labelGraphics._backgroundColor, time, defaultBackgroundColor, backgroundColor);
label.backgroundPadding = Property.getValueOrDefault(labelGraphics._backgroundPadding, time, defaultBackgroundPadding, backgroundPadding);
label.pixelOffset = Property.getValueOrDefault(labelGraphics._pixelOffset, time, defaultPixelOffset, pixelOffset);
label.eyeOffset = Property.getValueOrDefault(labelGraphics._eyeOffset, time, defaultEyeOffset, eyeOffset);
label.heightReference = Property.getValueOrDefault(labelGraphics._heightReference, time, defaultHeightReference);
label.horizontalOrigin = Property.getValueOrDefault(labelGraphics._horizontalOrigin, time, defaultHorizontalOrigin);
label.verticalOrigin = Property.getValueOrDefault(labelGraphics._verticalOrigin, time, defaultVerticalOrigin);
label.translucencyByDistance = Property.getValueOrUndefined(labelGraphics._translucencyByDistance, time, translucencyByDistance);
label.pixelOffsetScaleByDistance = Property.getValueOrUndefined(labelGraphics._pixelOffsetScaleByDistance, time, pixelOffsetScaleByDistance);
label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
}
return true;
};
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
LabelVisualizer.prototype.getBoundingSphere = function(entity, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var item = this._items.get(entity.id);
if (!defined(item) || !defined(item.label)) {
return BoundingSphereState.FAILED;
}
var label = item.label;
result.center = Cartesian3.clone(defaultValue(label._clampedPosition, label.position), result.center);
result.radius = 0;
return BoundingSphereState.DONE;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
LabelVisualizer.prototype.isDestroyed = function() {
return false;
};
/**
* Removes and destroys all primitives created by this instance.
*/
LabelVisualizer.prototype.destroy = function() {
this._entityCollection.collectionChanged.removeEventListener(LabelVisualizer.prototype._onCollectionChanged, this);
var entities = this._entityCollection.values;
for (var i = 0; i < entities.length; i++) {
this._cluster.removeLabel(entities[i]);
}
return destroyObject(this);
};
LabelVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
var i;
var entity;
var items = this._items;
var cluster = this._cluster;
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
if (defined(entity._label) && defined(entity._position)) {
items.set(entity.id, new EntityData(entity));
}
}
for (i = changed.length - 1; i > -1; i--) {
entity = changed[i];
if (defined(entity._label) && defined(entity._position)) {
if (!items.contains(entity.id)) {
items.set(entity.id, new EntityData(entity));
}
} else {
returnPrimitive(items.get(entity.id), entity, cluster);
items.remove(entity.id);
}
}
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
returnPrimitive(items.get(entity.id), entity, cluster);
items.remove(entity.id);
}
};
function returnPrimitive(item, entity, cluster) {
if (defined(item)) {
item.label = undefined;
cluster.removeLabel(entity);
}
}
return LabelVisualizer;
});
/*global define*/
define('ThirdParty/gltfDefaults',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/Quaternion',
'../Core/WebGLConstants'
], function(
Cartesian3,
defaultValue,
defined,
Quaternion,
WebGLConstants) {
"use strict";
function accessorDefaults(gltf) {
if (!defined(gltf.accessors)) {
gltf.accessors = {};
}
var accessors = gltf.accessors;
for (var name in accessors) {
if (accessors.hasOwnProperty(name)) {
var accessor = accessors[name];
accessor.byteStride = defaultValue(accessor.byteStride, 0);
}
}
}
function animationDefaults(gltf) {
if (!defined(gltf.animations)) {
gltf.animations = {};
}
var animations = gltf.animations;
for (var name in animations) {
if (animations.hasOwnProperty(name)) {
var animation = animations[name];
if (!defined(animation.channels)) {
animation.channels = [];
}
if (!defined(animation.parameters)) {
animation.parameters = {};
}
if (!defined(animation.samplers)) {
animation.samplers = {};
}
var samplers = animations.samplers;
for (var samplerName in samplers) {
if (samplers.hasOwnProperty(samplerName)) {
var sampler = samplers[samplerName];
sampler.interpolation = defaultValue(sampler.interpolation, 'LINEAR');
}
}
}
}
}
function assetDefaults(gltf) {
if (!defined(gltf.asset)) {
gltf.asset = {};
}
var asset = gltf.asset;
// Backwards compatibility for glTF 0.8. profile was a string.
if (!defined(asset.profile) || (typeof asset.profile === 'string')) {
asset.profile = {};
}
var profile = asset.profile;
asset.premultipliedAlpha = defaultValue(asset.premultipliedAlpha, false);
profile.api = defaultValue(profile.api, 'WebGL');
profile.version = defaultValue(profile.version, '1.0.2');
if (defined(gltf.version)) {
asset.version = defaultValue(asset.version, gltf.version);
delete gltf.version;
}
if (typeof asset.version === 'number') {
asset.version = asset.version.toFixed(1).toString();
}
}
function bufferDefaults(gltf) {
if (!defined(gltf.buffers)) {
gltf.buffers = {};
}
var buffers = gltf.buffers;
for (var name in buffers) {
if (buffers.hasOwnProperty(name)) {
var buffer = buffers[name];
buffer.type = defaultValue(buffer.type, 'arraybuffer');
}
}
}
function bufferViewDefaults(gltf) {
if (!defined(gltf.bufferViews)) {
gltf.bufferViews = {};
}
}
function cameraDefaults(gltf) {
if (!defined(gltf.cameras)) {
gltf.cameras = {};
}
}
function imageDefaults(gltf) {
if (!defined(gltf.images)) {
gltf.images = {};
}
}
function lightDefaults(gltf) {
if (!defined(gltf.extensions)) {
gltf.extensions = {};
}
var extensions = gltf.extensions;
if (!defined(extensions.KHR_materials_common)) {
extensions.KHR_materials_common = {};
}
var khrMaterialsCommon = extensions.KHR_materials_common;
if (defined(gltf.lights)) {
khrMaterialsCommon.lights = gltf.lights;
delete gltf.lights;
}
else if (!defined(khrMaterialsCommon.lights)) {
khrMaterialsCommon.lights = {};
}
var lights = khrMaterialsCommon.lights;
for (var name in lights) {
if (lights.hasOwnProperty(name)) {
var light = lights[name];
if (light.type === 'ambient') {
if (!defined(light.ambient)) {
light.ambient = {};
}
var ambientLight = light.ambient;
if (!defined(ambientLight.color)) {
ambientLight.color = [1.0, 1.0, 1.0];
}
} else if (light.type === 'directional') {
if (!defined(light.directional)) {
light.directional = {};
}
var directionalLight = light.directional;
if (!defined(directionalLight.color)) {
directionalLight.color = [1.0, 1.0, 1.0];
}
} else if (light.type === 'point') {
if (!defined(light.point)) {
light.point = {};
}
var pointLight = light.point;
if (!defined(pointLight.color)) {
pointLight.color = [1.0, 1.0, 1.0];
}
pointLight.constantAttenuation = defaultValue(pointLight.constantAttenuation, 1.0);
pointLight.linearAttenuation = defaultValue(pointLight.linearAttenuation, 0.0);
pointLight.quadraticAttenuation = defaultValue(pointLight.quadraticAttenuation, 0.0);
} else if (light.type === 'spot') {
if (!defined(light.spot)) {
light.spot = {};
}
var spotLight = light.spot;
if (!defined(spotLight.color)) {
spotLight.color = [1.0, 1.0, 1.0];
}
spotLight.constantAttenuation = defaultValue(spotLight.constantAttenuation, 1.0);
spotLight.fallOffAngle = defaultValue(spotLight.fallOffAngle, 3.14159265);
spotLight.fallOffExponent = defaultValue(spotLight.fallOffExponent, 0.0);
spotLight.linearAttenuation = defaultValue(spotLight.linearAttenuation, 0.0);
spotLight.quadraticAttenuation = defaultValue(spotLight.quadraticAttenuation, 0.0);
}
}
}
}
function materialDefaults(gltf) {
if (!defined(gltf.materials)) {
gltf.materials = {};
}
var materials = gltf.materials;
for (var name in materials) {
if (materials.hasOwnProperty(name)) {
var material = materials[name];
var instanceTechnique = material.instanceTechnique;
if (defined(instanceTechnique)) {
material.technique = instanceTechnique.technique;
material.values = instanceTechnique.values;
delete material.instanceTechnique;
}
if (!defined(material.extensions)) {
if (!defined(material.technique)) {
delete material.values;
material.extensions = {
KHR_materials_common : {
technique : 'CONSTANT',
transparent: false,
values : {
emission : {
type: WebGLConstants.FLOAT_VEC4,
value: [
0.5,
0.5,
0.5,
1
]
}
}
}
};
if (!defined(gltf.extensionsUsed)) {
gltf.extensionsUsed = [];
}
var extensionsUsed = gltf.extensionsUsed;
if (extensionsUsed.indexOf('KHR_materials_common') === -1) {
extensionsUsed.push('KHR_materials_common');
}
}
else if (!defined(material.values)) {
material.values = {};
}
}
}
}
}
function meshDefaults(gltf) {
if (!defined(gltf.meshes)) {
gltf.meshes = {};
}
var meshes = gltf.meshes;
for (var name in meshes) {
if (meshes.hasOwnProperty(name)) {
var mesh = meshes[name];
if (!defined(mesh.primitives)) {
mesh.primitives = [];
}
var primitives = mesh.primitives.length;
var length = primitives.length;
for (var i = 0; i < length; ++i) {
var primitive = primitives[i];
if (!defined(primitive.attributes)) {
primitive.attributes = {};
}
// Backwards compatibility for glTF 0.8. primitive was renamed to mode.
var defaultMode = defaultValue(primitive.primitive, WebGLConstants.TRIANGLES);
primitive.mode = defaultValue(primitive.mode, defaultMode);
}
}
}
}
function nodeDefaults(gltf) {
if (!defined(gltf.nodes)) {
gltf.nodes = {};
}
var nodes = gltf.nodes;
var hasAxisAngle = (parseFloat(gltf.asset.version) < 1.0);
var axis = new Cartesian3();
var quat = new Quaternion();
for (var name in nodes) {
if (nodes.hasOwnProperty(name)) {
var node = nodes[name];
if (!defined(node.children)) {
node.children = [];
}
if (hasAxisAngle && defined(node.rotation)) {
var rotation = node.rotation;
Cartesian3.fromArray(rotation, 0, axis);
Quaternion.fromAxisAngle(axis, rotation[3], quat);
node.rotation = [quat.x, quat.y, quat.z, quat.w];
}
if (!defined(node.matrix)) {
// Add default identity matrix if there is no matrix property and no TRS properties
if (!defined(node.translation) && !defined(node.rotation) && !defined(node.scale)) {
node.matrix = [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
];
} else {
if (!defined(node.translation)) {
node.translation = [0.0, 0.0, 0.0];
}
if (!defined(node.rotation)) {
node.rotation = [0.0, 0.0, 0.0, 1.0];
}
if (!defined(node.scale)) {
node.scale = [1.0, 1.0, 1.0];
}
}
}
var instanceSkin = node.instanceSkin;
if (defined(instanceSkin)) {
node.skeletons = instanceSkin.skeletons;
node.skin = instanceSkin.skin;
node.meshes = instanceSkin.meshes;
delete node.instanceSkin;
}
}
}
}
function programDefaults(gltf) {
if (!defined(gltf.programs)) {
gltf.programs = {};
}
var programs = gltf.programs;
for (var name in programs) {
if (programs.hasOwnProperty(name)) {
var program = programs[name];
if (!defined(program.attributes)) {
program.attributes = [];
}
}
}
}
function samplerDefaults(gltf) {
if (!defined(gltf.samplers)) {
gltf.samplers = {};
}
var samplers = gltf.samplers;
for (var name in samplers) {
if (samplers.hasOwnProperty(name)) {
var sampler = samplers[name];
sampler.magFilter = defaultValue(sampler.magFilter, WebGLConstants.LINEAR);
sampler.minFilter = defaultValue(sampler.minFilter, WebGLConstants.NEAREST_MIPMAP_LINEAR);
sampler.wrapS = defaultValue(sampler.wrapS, WebGLConstants.REPEAT);
sampler.wrapT = defaultValue(sampler.wrapT, WebGLConstants.REPEAT);
}
}
}
function sceneDefaults(gltf) {
if (!defined(gltf.scenes)) {
gltf.scenes = {};
}
var scenes = gltf.scenes;
for (var name in scenes) {
if (scenes.hasOwnProperty(name)) {
var scene = scenes[name];
if (!defined(scene.node)) {
scene.node = [];
}
}
}
}
function shaderDefaults(gltf) {
if (!defined(gltf.shaders)) {
gltf.shaders = {};
}
}
function skinDefaults(gltf) {
if (!defined(gltf.skins)) {
gltf.skins = {};
}
var skins = gltf.skins;
for (var name in skins) {
if (skins.hasOwnProperty(name)) {
var skin = skins[name];
if (!defined(skin.bindShapeMatrix)) {
skin.bindShapeMatrix = [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
];
}
}
}
}
function statesDefaults(states) {
if (!defined(states.enable)) {
states.enable = [];
}
if (!defined(states.disable)) {
states.disable = [];
}
}
function techniqueDefaults(gltf) {
if (!defined(gltf.techniques)) {
gltf.techniques = {};
}
var techniques = gltf.techniques;
for (var name in techniques) {
if (techniques.hasOwnProperty(name)) {
var technique = techniques[name];
if (!defined(technique.parameters)) {
technique.parameters = {};
}
var parameters = technique.parameters;
for (var parameterName in parameters) {
var parameter = parameters[parameterName];
parameter.node = defaultValue(parameter.node, parameter.source);
parameter.source = undefined;
}
var passes = technique.passes;
if (defined(passes)) {
var passName = defaultValue(technique.pass, 'defaultPass');
if (passes.hasOwnProperty(passName)) {
var pass = passes[passName];
var instanceProgram = pass.instanceProgram;
technique.attributes = defaultValue(technique.attributes, instanceProgram.attributes);
technique.program = defaultValue(technique.program, instanceProgram.program);
technique.uniforms = defaultValue(technique.uniforms, instanceProgram.uniforms);
technique.states = defaultValue(technique.states, pass.states);
}
technique.passes = undefined;
technique.pass = undefined;
}
if (!defined(technique.attributes)) {
technique.attributes = {};
}
if (!defined(technique.uniforms)) {
technique.uniforms = {};
}
if (!defined(technique.states)) {
technique.states = {};
}
statesDefaults(technique.states);
}
}
}
function textureDefaults(gltf) {
if (!defined(gltf.textures)) {
gltf.textures = {};
}
var textures = gltf.textures;
for (var name in textures) {
if (textures.hasOwnProperty(name)) {
var texture = textures[name];
texture.format = defaultValue(texture.format, WebGLConstants.RGBA);
texture.internalFormat = defaultValue(texture.internalFormat, texture.format);
texture.target = defaultValue(texture.target, WebGLConstants.TEXTURE_2D);
texture.type = defaultValue(texture.type, WebGLConstants.UNSIGNED_BYTE);
}
}
}
/**
* Modifies gltf in place.
*
* @private
*/
var gltfDefaults = function(gltf) {
if (!defined(gltf)) {
return undefined;
}
if (defined(gltf.allExtensions)) {
gltf.extensionsUsed = gltf.allExtensions;
gltf.allExtensions = undefined;
}
gltf.extensionsUsed = defaultValue(gltf.extensionsUsed, []);
accessorDefaults(gltf);
animationDefaults(gltf);
assetDefaults(gltf);
bufferDefaults(gltf);
bufferViewDefaults(gltf);
cameraDefaults(gltf);
imageDefaults(gltf);
lightDefaults(gltf);
materialDefaults(gltf);
meshDefaults(gltf);
nodeDefaults(gltf);
programDefaults(gltf);
samplerDefaults(gltf);
sceneDefaults(gltf);
shaderDefaults(gltf);
skinDefaults(gltf);
techniqueDefaults(gltf);
textureDefaults(gltf);
return gltf;
};
return gltfDefaults;
});
/*global define*/
define('Scene/getAttributeOrUniformBySemantic',[], function() {
'use strict';
/**
* Return the uniform or attribute that has the given semantic.
*
* @private
*/
function getAttributeOrUniformBySemantic(gltf, semantic) {
var techniques = gltf.techniques;
for (var techniqueName in techniques) {
if (techniques.hasOwnProperty(techniqueName)) {
var technique = techniques[techniqueName];
var parameters = technique.parameters;
var attributes = technique.attributes;
var uniforms = technique.uniforms;
for (var attributeName in attributes) {
if (attributes.hasOwnProperty(attributeName)) {
if (parameters[attributes[attributeName]].semantic === semantic) {
return attributeName;
}
}
}
for (var uniformName in uniforms) {
if (uniforms.hasOwnProperty(uniformName)) {
if (parameters[uniforms[uniformName]].semantic === semantic) {
return uniformName;
}
}
}
}
}
return undefined;
}
return getAttributeOrUniformBySemantic;
});
/*global define*/
define('Scene/getBinaryAccessor',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/ComponentDatatype',
'../Core/Matrix2',
'../Core/Matrix3',
'../Core/Matrix4'
], function(
Cartesian2,
Cartesian3,
Cartesian4,
ComponentDatatype,
Matrix2,
Matrix3,
Matrix4) {
'use strict';
var ComponentsPerAttribute = {
SCALAR : 1,
VEC2 : 2,
VEC3 : 3,
VEC4 : 4,
MAT2 : 4,
MAT3 : 9,
MAT4 : 16
};
var ClassPerType = {
SCALAR : undefined,
VEC2 : Cartesian2,
VEC3 : Cartesian3,
VEC4 : Cartesian4,
MAT2 : Matrix2,
MAT3 : Matrix3,
MAT4 : Matrix4
};
/**
* @private
*/
function getBinaryAccessor(accessor) {
var componentType = accessor.componentType;
var componentDatatype;
if (typeof componentType === 'string') {
componentDatatype = ComponentDatatype.fromName(componentType);
} else {
componentDatatype = componentType;
}
var componentsPerAttribute = ComponentsPerAttribute[accessor.type];
var classType = ClassPerType[accessor.type];
return {
componentsPerAttribute : componentsPerAttribute,
classType : classType,
createArrayBufferView : function(buffer, byteOffset, length) {
return ComponentDatatype.createArrayBufferView(componentDatatype, buffer, byteOffset, componentsPerAttribute * length);
}
};
}
return getBinaryAccessor;
});
/*global define*/
define('Scene/ModelAnimationCache',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/LinearSpline',
'../Core/Matrix4',
'../Core/Quaternion',
'../Core/QuaternionSpline',
'../Core/WebGLConstants',
'./getBinaryAccessor'
], function(
Cartesian3,
defaultValue,
defined,
LinearSpline,
Matrix4,
Quaternion,
QuaternionSpline,
WebGLConstants,
getBinaryAccessor) {
'use strict';
/**
* @private
*/
function ModelAnimationCache() {
}
function getAccessorKey(model, accessor) {
var gltf = model.gltf;
var buffers = gltf.buffers;
var bufferViews = gltf.bufferViews;
var bufferView = bufferViews[accessor.bufferView];
var buffer = buffers[bufferView.buffer];
var byteOffset = bufferView.byteOffset + accessor.byteOffset;
var byteLength = accessor.count * getBinaryAccessor(accessor).componentsPerAttribute;
// buffer.path will be undefined when animations are embedded.
return model.cacheKey + '//' + defaultValue(buffer.path, '') + '/' + byteOffset + '/' + byteLength;
}
var cachedAnimationParameters = {
};
var axisScratch = new Cartesian3();
ModelAnimationCache.getAnimationParameterValues = function(model, accessor) {
var key = getAccessorKey(model, accessor);
var values = cachedAnimationParameters[key];
if (!defined(values)) {
// Cache miss
var loadResources = model._loadResources;
var gltf = model.gltf;
var hasAxisAngle = (parseFloat(gltf.asset.version) < 1.0);
var bufferViews = gltf.bufferViews;
var bufferView = bufferViews[accessor.bufferView];
var componentType = accessor.componentType;
var type = accessor.type;
var count = accessor.count;
// Convert typed array to Cesium types
var buffer = loadResources.getBuffer(bufferView);
var typedArray = getBinaryAccessor(accessor).createArrayBufferView(buffer.buffer, buffer.byteOffset + accessor.byteOffset, count);
var i;
if ((componentType === WebGLConstants.FLOAT) && (type === 'SCALAR')) {
values = typedArray;
}
else if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC3')) {
values = new Array(count);
for (i = 0; i < count; ++i) {
values[i] = Cartesian3.fromArray(typedArray, 3 * i);
}
} else if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC4')) {
values = new Array(count);
for (i = 0; i < count; ++i) {
var byteOffset = 4 * i;
if (hasAxisAngle) {
values[i] = Quaternion.fromAxisAngle(Cartesian3.fromArray(typedArray, byteOffset, axisScratch), typedArray[byteOffset + 3]);
}
else {
values[i] = Quaternion.unpack(typedArray, byteOffset);
}
}
}
// GLTF_SPEC: Support more parameter types when glTF supports targeting materials. https://github.com/KhronosGroup/glTF/issues/142
if (defined(model.cacheKey)) {
// Only cache when we can create a unique id
cachedAnimationParameters[key] = values;
}
}
return values;
};
var cachedAnimationSplines = {
};
function getAnimationSplineKey(model, animationName, samplerName) {
return model.cacheKey + '//' + animationName + '/' + samplerName;
}
// GLTF_SPEC: https://github.com/KhronosGroup/glTF/issues/185
function ConstantSpline(value) {
this._value = value;
}
ConstantSpline.prototype.evaluate = function(time, result) {
return this._value;
};
// END GLTF_SPEC
ModelAnimationCache.getAnimationSpline = function(model, animationName, animation, samplerName, sampler, parameterValues) {
var key = getAnimationSplineKey(model, animationName, samplerName);
var spline = cachedAnimationSplines[key];
if (!defined(spline)) {
var times = parameterValues[sampler.input];
var accessor = model.gltf.accessors[animation.parameters[sampler.output]];
var controlPoints = parameterValues[sampler.output];
// GLTF_SPEC: https://github.com/KhronosGroup/glTF/issues/185
if ((times.length === 1) && (controlPoints.length === 1)) {
spline = new ConstantSpline(controlPoints[0]);
} else {
// END GLTF_SPEC
var componentType = accessor.componentType;
var type = accessor.type;
if (sampler.interpolation === 'LINEAR') {
if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC3')) {
spline = new LinearSpline({
times : times,
points : controlPoints
});
} else if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC4')) {
spline = new QuaternionSpline({
times : times,
points : controlPoints
});
}
// GLTF_SPEC: Support more parameter types when glTF supports targeting materials. https://github.com/KhronosGroup/glTF/issues/142
}
// GLTF_SPEC: Support new interpolators. https://github.com/KhronosGroup/glTF/issues/156
}
if (defined(model.cacheKey)) {
// Only cache when we can create a unique id
cachedAnimationSplines[key] = spline;
}
}
return spline;
};
var cachedSkinInverseBindMatrices = {
};
ModelAnimationCache.getSkinInverseBindMatrices = function(model, accessor) {
var key = getAccessorKey(model, accessor);
var matrices = cachedSkinInverseBindMatrices[key];
if (!defined(matrices)) {
// Cache miss
var loadResources = model._loadResources;
var gltf = model.gltf;
var bufferViews = gltf.bufferViews;
var bufferView = bufferViews[accessor.bufferView];
var componentType = accessor.componentType;
var type = accessor.type;
var count = accessor.count;
var buffer = loadResources.getBuffer(bufferView);
var typedArray = getBinaryAccessor(accessor).createArrayBufferView(buffer.buffer, buffer.byteOffset + accessor.byteOffset, count);
matrices = new Array(count);
if ((componentType === WebGLConstants.FLOAT) && (type === 'MAT4')) {
for (var i = 0; i < count; ++i) {
matrices[i] = Matrix4.fromArray(typedArray, 16 * i);
}
}
cachedSkinInverseBindMatrices[key] = matrices;
}
return matrices;
};
return ModelAnimationCache;
});
/*global define*/
define('Scene/ModelAnimationLoop',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Determines if and how a glTF animation is looped.
*
* @exports ModelAnimationLoop
*
* @see ModelAnimationCollection#add
*/
var ModelAnimationLoop = {
/**
* Play the animation once; do not loop it.
*
* @type {Number}
* @constant
*/
NONE : 0,
/**
* Loop the animation playing it from the start immediately after it stops.
*
* @type {Number}
* @constant
*/
REPEAT : 1,
/**
* Loop the animation. First, playing it forward, then in reverse, then forward, and so on.
*
* @type {Number}
* @constant
*/
MIRRORED_REPEAT : 2
};
return freezeObject(ModelAnimationLoop);
});
/*global define*/
define('Scene/ModelAnimationState',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* @private
*/
return freezeObject({
STOPPED : 0,
ANIMATING : 1
});
});
/*global define*/
define('Scene/ModelAnimation',[
'../Core/defaultValue',
'../Core/defineProperties',
'../Core/Event',
'../Core/JulianDate',
'./ModelAnimationLoop',
'./ModelAnimationState'
], function(
defaultValue,
defineProperties,
Event,
JulianDate,
ModelAnimationLoop,
ModelAnimationState) {
'use strict';
/**
* An active glTF animation. A glTF asset can contain animations. An active animation
* is an animation that is currently playing or scheduled to be played because it was
* added to a model's {@link ModelAnimationCollection}. An active animation is an
* instance of an animation; for example, there can be multiple active animations
* for the same glTF animation, each with a different start time.
*
* Create this by calling {@link ModelAnimationCollection#add}.
*
*
* @alias ModelAnimation
* @internalConstructor
*
* @see ModelAnimationCollection#add
*/
function ModelAnimation(options, model, runtimeAnimation) {
this._name = options.name;
this._startTime = JulianDate.clone(options.startTime);
this._delay = defaultValue(options.delay, 0.0); // in seconds
this._stopTime = options.stopTime;
/**
* When true
, the animation is removed after it stops playing.
* This is slightly more efficient that not removing it, but if, for example,
* time is reversed, the animation is not played again.
*
* @type {Boolean}
* @default false
*/
this.removeOnStop = defaultValue(options.removeOnStop, false);
this._speedup = defaultValue(options.speedup, 1.0);
this._reverse = defaultValue(options.reverse, false);
this._loop = defaultValue(options.loop, ModelAnimationLoop.NONE);
/**
* The event fired when this animation is started. This can be used, for
* example, to play a sound or start a particle system, when the animation starts.
*
* This event is fired at the end of the frame after the scene is rendered.
*
*
* @type {Event}
* @default new Event()
*
* @example
* animation.start.addEventListener(function(model, animation) {
* console.log('Animation started: ' + animation.name);
* });
*/
this.start = new Event();
/**
* The event fired when on each frame when this animation is updated. The
* current time of the animation, relative to the glTF animation time span, is
* passed to the event, which allows, for example, starting new animations at a
* specific time relative to a playing animation.
*
* This event is fired at the end of the frame after the scene is rendered.
*
*
* @type {Event}
* @default new Event()
*
* @example
* animation.update.addEventListener(function(model, animation, time) {
* console.log('Animation updated: ' + animation.name + '. glTF animation time: ' + time);
* });
*/
this.update = new Event();
/**
* The event fired when this animation is stopped. This can be used, for
* example, to play a sound or start a particle system, when the animation stops.
*
* This event is fired at the end of the frame after the scene is rendered.
*
*
* @type {Event}
* @default new Event()
*
* @example
* animation.stop.addEventListener(function(model, animation) {
* console.log('Animation stopped: ' + animation.name);
* });
*/
this.stop = new Event();
this._state = ModelAnimationState.STOPPED;
this._runtimeAnimation = runtimeAnimation;
// Set during animation update
this._computedStartTime = undefined;
this._duration = undefined;
// To avoid allocations in ModelAnimationCollection.update
var that = this;
this._raiseStartEvent = function() {
that.start.raiseEvent(model, that);
};
this._updateEventTime = 0.0;
this._raiseUpdateEvent = function() {
that.update.raiseEvent(model, that, that._updateEventTime);
};
this._raiseStopEvent = function() {
that.stop.raiseEvent(model, that);
};
}
defineProperties(ModelAnimation.prototype, {
/**
* The glTF animation name that identifies this animation.
*
* @memberof ModelAnimation.prototype
*
* @type {String}
* @readonly
*/
name : {
get : function() {
return this._name;
}
},
/**
* The scene time to start playing this animation. When this is undefined
,
* the animation starts at the next frame.
*
* @memberof ModelAnimation.prototype
*
* @type {JulianDate}
* @readonly
*
* @default undefined
*/
startTime : {
get : function() {
return this._startTime;
}
},
/**
* The delay, in seconds, from {@link ModelAnimation#startTime} to start playing.
*
* @memberof ModelAnimation.prototype
*
* @type {Number}
* @readonly
*
* @default undefined
*/
delay : {
get : function() {
return this._delay;
}
},
/**
* The scene time to stop playing this animation. When this is undefined
,
* the animation is played for its full duration and perhaps repeated depending on
* {@link ModelAnimation#loop}.
*
* @memberof ModelAnimation.prototype
*
* @type {JulianDate}
* @readonly
*
* @default undefined
*/
stopTime : {
get : function() {
return this._stopTime;
}
},
/**
* Values greater than 1.0
increase the speed that the animation is played relative
* to the scene clock speed; values less than 1.0
decrease the speed. A value of
* 1.0
plays the animation at the speed in the glTF animation mapped to the scene
* clock speed. For example, if the scene is played at 2x real-time, a two-second glTF animation
* will play in one second even if speedup
is 1.0
.
*
* @memberof ModelAnimation.prototype
*
* @type {Number}
* @readonly
*
* @default 1.0
*/
speedup : {
get : function() {
return this._speedup;
}
},
/**
* When true
, the animation is played in reverse.
*
* @memberof ModelAnimation.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
reverse : {
get : function() {
return this._reverse;
}
},
/**
* Determines if and how the animation is looped.
*
* @memberof ModelAnimation.prototype
*
* @type {ModelAnimationLoop}
* @readonly
*
* @default {@link ModelAnimationLoop.NONE}
*/
loop : {
get : function() {
return this._loop;
}
}
});
return ModelAnimation;
});
/*global define*/
define('Scene/ModelAnimationCollection',[
'../Core/clone',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/JulianDate',
'../Core/Math',
'./ModelAnimation',
'./ModelAnimationLoop',
'./ModelAnimationState'
], function(
clone,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
JulianDate,
CesiumMath,
ModelAnimation,
ModelAnimationLoop,
ModelAnimationState) {
'use strict';
/**
* A collection of active model animations. Access this using {@link Model#activeAnimations}.
*
* @alias ModelAnimationCollection
* @internalConstructor
*
* @see Model#activeAnimations
*/
function ModelAnimationCollection(model) {
/**
* The event fired when an animation is added to the collection. This can be used, for
* example, to keep a UI in sync.
*
* @type {Event}
* @default new Event()
*
* @example
* model.activeAnimations.animationAdded.addEventListener(function(model, animation) {
* console.log('Animation added: ' + animation.name);
* });
*/
this.animationAdded = new Event();
/**
* The event fired when an animation is removed from the collection. This can be used, for
* example, to keep a UI in sync.
*
* @type {Event}
* @default new Event()
*
* @example
* model.activeAnimations.animationRemoved.addEventListener(function(model, animation) {
* console.log('Animation removed: ' + animation.name);
* });
*/
this.animationRemoved = new Event();
this._model = model;
this._scheduledAnimations = [];
this._previousTime = undefined;
}
defineProperties(ModelAnimationCollection.prototype, {
/**
* The number of animations in the collection.
*
* @memberof ModelAnimationCollection.prototype
*
* @type {Number}
* @readonly
*/
length : {
get : function() {
return this._scheduledAnimations.length;
}
}
});
/**
* Creates and adds an animation with the specified initial properties to the collection.
*
* This raises the {@link ModelAnimationCollection#animationAdded} event so, for example, a UI can stay in sync.
*
*
* @param {Object} options Object with the following properties:
* @param {String} options.name The glTF animation name that identifies the animation.
* @param {JulianDate} [options.startTime] The scene time to start playing the animation. When this is undefined
, the animation starts at the next frame.
* @param {Number} [options.delay=0.0] The delay, in seconds, from startTime
to start playing.
* @param {JulianDate} [options.stopTime] The scene time to stop playing the animation. When this is undefined
, the animation is played for its full duration.
* @param {Boolean} [options.removeOnStop=false] When true
, the animation is removed after it stops playing.
* @param {Number} [options.speedup=1.0] Values greater than 1.0
increase the speed that the animation is played relative to the scene clock speed; values less than 1.0
decrease the speed.
* @param {Boolean} [options.reverse=false] When true
, the animation is played in reverse.
* @param {ModelAnimationLoop} [options.loop=ModelAnimationLoop.NONE] Determines if and how the animation is looped.
* @returns {ModelAnimation} The animation that was added to the collection.
*
* @exception {DeveloperError} Animations are not loaded. Wait for the {@link Model#readyPromise} to resolve.
* @exception {DeveloperError} options.name must be a valid animation name.
* @exception {DeveloperError} options.speedup must be greater than zero.
*
* @example
* // Example 1. Add an animation
* model.activeAnimations.add({
* name : 'animation name'
* });
*
* @example
* // Example 2. Add an animation and provide all properties and events
* var startTime = Cesium.JulianDate.now();
*
* var animation = model.activeAnimations.add({
* name : 'another animation name',
* startTime : startTime,
* delay : 0.0, // Play at startTime (default)
* stopTime : Cesium.JulianDate.addSeconds(startTime, 4.0, new Cesium.JulianDate()),
* removeOnStop : false, // Do not remove when animation stops (default)
* speedup : 2.0, // Play at double speed
* reverse : true, // Play in reverse
* loop : Cesium.ModelAnimationLoop.REPEAT // Loop the animation
* });
*
* animation.start.addEventListener(function(model, animation) {
* console.log('Animation started: ' + animation.name);
* });
* animation.update.addEventListener(function(model, animation, time) {
* console.log('Animation updated: ' + animation.name + '. glTF animation time: ' + time);
* });
* animation.stop.addEventListener(function(model, animation) {
* console.log('Animation stopped: ' + animation.name);
* });
*/
ModelAnimationCollection.prototype.add = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var model = this._model;
var animations = model._runtime.animations;
if (!defined(animations)) {
throw new DeveloperError('Animations are not loaded. Wait for Model.readyPromise to resolve.');
}
var animation = animations[options.name];
if (!defined(animation)) {
throw new DeveloperError('options.name must be a valid animation name.');
}
if (defined(options.speedup) && (options.speedup <= 0.0)) {
throw new DeveloperError('options.speedup must be greater than zero.');
}
var scheduledAnimation = new ModelAnimation(options, model, animation);
this._scheduledAnimations.push(scheduledAnimation);
this.animationAdded.raiseEvent(model, scheduledAnimation);
return scheduledAnimation;
};
/**
* Creates and adds an animation with the specified initial properties to the collection
* for each animation in the model.
*
* This raises the {@link ModelAnimationCollection#animationAdded} event for each model so, for example, a UI can stay in sync.
*
*
* @param {Object} [options] Object with the following properties:
* @param {JulianDate} [options.startTime] The scene time to start playing the animations. When this is undefined
, the animations starts at the next frame.
* @param {Number} [options.delay=0.0] The delay, in seconds, from startTime
to start playing.
* @param {JulianDate} [options.stopTime] The scene time to stop playing the animations. When this is undefined
, the animations are played for its full duration.
* @param {Boolean} [options.removeOnStop=false] When true
, the animations are removed after they stop playing.
* @param {Number} [options.speedup=1.0] Values greater than 1.0
increase the speed that the animations play relative to the scene clock speed; values less than 1.0
decrease the speed.
* @param {Boolean} [options.reverse=false] When true
, the animations are played in reverse.
* @param {ModelAnimationLoop} [options.loop=ModelAnimationLoop.NONE] Determines if and how the animations are looped.
* @returns {ModelAnimation[]} An array of {@link ModelAnimation} objects, one for each animation added to the collection. If there are no glTF animations, the array is empty.
*
* @exception {DeveloperError} Animations are not loaded. Wait for the {@link Model#readyPromise} to resolve.
* @exception {DeveloperError} options.speedup must be greater than zero.
*
* @example
* model.activeAnimations.addAll({
* speedup : 0.5, // Play at half-speed
* loop : Cesium.ModelAnimationLoop.REPEAT // Loop the animations
* });
*/
ModelAnimationCollection.prototype.addAll = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(this._model._runtime.animations)) {
throw new DeveloperError('Animations are not loaded. Wait for Model.readyPromise to resolve.');
}
if (defined(options.speedup) && (options.speedup <= 0.0)) {
throw new DeveloperError('options.speedup must be greater than zero.');
}
options = clone(options);
var scheduledAnimations = [];
var animationIds = this._model._animationIds;
var length = animationIds.length;
for (var i = 0; i < length; ++i) {
options.name = animationIds[i];
scheduledAnimations.push(this.add(options));
}
return scheduledAnimations;
};
/**
* Removes an animation from the collection.
*
* This raises the {@link ModelAnimationCollection#animationRemoved} event so, for example, a UI can stay in sync.
*
*
* An animation can also be implicitly removed from the collection by setting {@link ModelAnimation#removeOnStop} to
* true
. The {@link ModelAnimationCollection#animationRemoved} event is still fired when the animation is removed.
*
*
* @param {ModelAnimation} animation The animation to remove.
* @returns {Boolean} true
if the animation was removed; false
if the animation was not found in the collection.
*
* @example
* var a = model.activeAnimations.add({
* name : 'animation name'
* });
* model.activeAnimations.remove(a); // Returns true
*/
ModelAnimationCollection.prototype.remove = function(animation) {
if (defined(animation)) {
var animations = this._scheduledAnimations;
var i = animations.indexOf(animation);
if (i !== -1) {
animations.splice(i, 1);
this.animationRemoved.raiseEvent(this._model, animation);
return true;
}
}
return false;
};
/**
* Removes all animations from the collection.
*
* This raises the {@link ModelAnimationCollection#animationRemoved} event for each
* animation so, for example, a UI can stay in sync.
*
*/
ModelAnimationCollection.prototype.removeAll = function() {
var model = this._model;
var animations = this._scheduledAnimations;
var length = animations.length;
this._scheduledAnimations = [];
for (var i = 0; i < length; ++i) {
this.animationRemoved.raiseEvent(model, animations[i]);
}
};
/**
* Determines whether this collection contains a given animation.
*
* @param {ModelAnimation} animation The animation to check for.
* @returns {Boolean} true
if this collection contains the animation, false
otherwise.
*/
ModelAnimationCollection.prototype.contains = function(animation) {
if (defined(animation)) {
return (this._scheduledAnimations.indexOf(animation) !== -1);
}
return false;
};
/**
* Returns the animation in the collection at the specified index. Indices are zero-based
* and increase as animations are added. Removing an animation shifts all animations after
* it to the left, changing their indices. This function is commonly used to iterate over
* all the animations in the collection.
*
* @param {Number} index The zero-based index of the animation.
* @returns {ModelAnimation} The animation at the specified index.
*
* @example
* // Output the names of all the animations in the collection.
* var animations = model.activeAnimations;
* var length = animations.length;
* for (var i = 0; i < length; ++i) {
* console.log(animations.get(i).name);
* }
*/
ModelAnimationCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._scheduledAnimations[index];
};
function animateChannels(runtimeAnimation, localAnimationTime) {
var channelEvaluators = runtimeAnimation.channelEvaluators;
var length = channelEvaluators.length;
for (var i = 0; i < length; ++i) {
channelEvaluators[i](localAnimationTime);
}
}
var animationsToRemove = [];
function createAnimationRemovedFunction(modelAnimationCollection, model, animation) {
return function() {
modelAnimationCollection.animationRemoved.raiseEvent(model, animation);
};
}
/**
* @private
*/
ModelAnimationCollection.prototype.update = function(frameState) {
var scheduledAnimations = this._scheduledAnimations;
var length = scheduledAnimations.length;
if (length === 0) {
// No animations - quick return for performance
this._previousTime = undefined;
return false;
}
if (JulianDate.equals(frameState.time, this._previousTime)) {
// Animations are currently only time-dependent so do not animate when paused or picking
return false;
}
this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
var animationOccured = false;
var sceneTime = frameState.time;
var model = this._model;
for (var i = 0; i < length; ++i) {
var scheduledAnimation = scheduledAnimations[i];
var runtimeAnimation = scheduledAnimation._runtimeAnimation;
if (!defined(scheduledAnimation._computedStartTime)) {
scheduledAnimation._computedStartTime = JulianDate.addSeconds(defaultValue(scheduledAnimation.startTime, sceneTime), scheduledAnimation.delay, new JulianDate());
}
if (!defined(scheduledAnimation._duration)) {
scheduledAnimation._duration = runtimeAnimation.stopTime * (1.0 / scheduledAnimation.speedup);
}
var startTime = scheduledAnimation._computedStartTime;
var duration = scheduledAnimation._duration;
var stopTime = scheduledAnimation.stopTime;
// [0.0, 1.0] normalized local animation time
var delta = (duration !== 0.0) ? (JulianDate.secondsDifference(sceneTime, startTime) / duration) : 0.0;
var pastStartTime = (delta >= 0.0);
// Play animation if
// * we are after the start time or the animation is being repeated, and
// * before the end of the animation's duration or the animation is being repeated, and
// * we did not reach a user-provided stop time.
var repeat = ((scheduledAnimation.loop === ModelAnimationLoop.REPEAT) ||
(scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT));
var play = (pastStartTime || (repeat && !defined(scheduledAnimation.startTime))) &&
((delta <= 1.0) || repeat) &&
(!defined(stopTime) || JulianDate.lessThanOrEquals(sceneTime, stopTime));
if (play) {
// STOPPED -> ANIMATING state transition?
if (scheduledAnimation._state === ModelAnimationState.STOPPED) {
scheduledAnimation._state = ModelAnimationState.ANIMATING;
if (scheduledAnimation.start.numberOfListeners > 0) {
frameState.afterRender.push(scheduledAnimation._raiseStartEvent);
}
}
// Truncate to [0.0, 1.0] for repeating animations
if (scheduledAnimation.loop === ModelAnimationLoop.REPEAT) {
delta = delta - Math.floor(delta);
} else if (scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT) {
var floor = Math.floor(delta);
var fract = delta - floor;
// When even use (1.0 - fract) to mirror repeat
delta = (floor % 2 === 1.0) ? (1.0 - fract) : fract;
}
if (scheduledAnimation.reverse) {
delta = 1.0 - delta;
}
var localAnimationTime = delta * duration * scheduledAnimation.speedup;
// Clamp in case floating-point roundoff goes outside the animation's first or last keyframe
localAnimationTime = CesiumMath.clamp(localAnimationTime, runtimeAnimation.startTime, runtimeAnimation.stopTime);
animateChannels(runtimeAnimation, localAnimationTime);
if (scheduledAnimation.update.numberOfListeners > 0) {
scheduledAnimation._updateEventTime = localAnimationTime;
frameState.afterRender.push(scheduledAnimation._raiseUpdateEvent);
}
animationOccured = true;
} else {
// ANIMATING -> STOPPED state transition?
if (pastStartTime && (scheduledAnimation._state === ModelAnimationState.ANIMATING)) {
scheduledAnimation._state = ModelAnimationState.STOPPED;
if (scheduledAnimation.stop.numberOfListeners > 0) {
frameState.afterRender.push(scheduledAnimation._raiseStopEvent);
}
if (scheduledAnimation.removeOnStop) {
animationsToRemove.push(scheduledAnimation);
}
}
}
}
// Remove animations that stopped
length = animationsToRemove.length;
for (var j = 0; j < length; ++j) {
var animationToRemove = animationsToRemove[j];
scheduledAnimations.splice(scheduledAnimations.indexOf(animationToRemove), 1);
frameState.afterRender.push(createAnimationRemovedFunction(this, model, animationToRemove));
}
animationsToRemove.length = 0;
return animationOccured;
};
return ModelAnimationCollection;
});
/*global define*/
define('Scene/ModelMaterial',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError'
], function(
defined,
defineProperties,
DeveloperError) {
'use strict';
/**
* A model's material with modifiable parameters. A glTF material
* contains parameters defined by the material's technique with values
* defined by the technique and potentially overridden by the material.
* This class allows changing these values at runtime.
*
* Use {@link Model#getMaterial} to create an instance.
*
*
* @alias ModelMaterial
* @internalConstructor
*
* @see Model#getMaterial
*/
function ModelMaterial(model, material, id) {
this._name = material.name;
this._id = id;
this._uniformMap = model._uniformMaps[id];
}
defineProperties(ModelMaterial.prototype, {
/**
* The value of the name
property of this material. This is the
* name assigned by the artist when the asset is created. This can be
* different than the name of the material property ({@link ModelMaterial#id}),
* which is internal to glTF.
*
* @memberof ModelMaterial.prototype
*
* @type {String}
* @readonly
*/
name : {
get : function() {
return this._name;
}
},
/**
* The name of the glTF JSON property for this material. This is guaranteed
* to be unique among all materials. It may not match the material's
* name
property (@link ModelMaterial#name), which is assigned by
* the artist when the asset is created.
*
* @memberof ModelMaterial.prototype
*
* @type {String}
* @readonly
*/
id : {
get : function() {
return this._id;
}
}
});
/**
* Assigns a value to a material parameter. The type for value
* depends on the glTF type of the parameter. It will be a floating-point
* number, Cartesian, or matrix.
*
* @param {String} name The name of the parameter.
* @param {Object} [value] The value to assign to the parameter.
*
* @exception {DeveloperError} name must match a parameter name in the material's technique that is targetable and not optimized out.
*
* @example
* material.setValue('diffuse', new Cesium.Cartesian4(1.0, 0.0, 0.0, 1.0)); // vec4
* material.setValue('shininess', 256.0); // scalar
*/
ModelMaterial.prototype.setValue = function(name, value) {
if (!defined(name)) {
throw new DeveloperError('name is required.');
}
var v = this._uniformMap.values[name];
if (!defined(v)) {
throw new DeveloperError('name must match a parameter name in the material\'s technique that is targetable and not optimized out.');
}
v.value = v.clone(value, v.value);
};
/**
* Returns the value of the parameter with the given name
. The type of the
* returned object depends on the glTF type of the parameter. It will be a floating-point
* number, Cartesian, or matrix.
*
* @param {String} name The name of the parameter.
* @returns {Object} The value of the parameter or undefined
if the parameter does not exist.
*/
ModelMaterial.prototype.getValue = function(name) {
if (!defined(name)) {
throw new DeveloperError('name is required.');
}
var v = this._uniformMap.values[name];
if (!defined(v)) {
return undefined;
}
return v.value;
};
return ModelMaterial;
});
/*global define*/
define('Scene/modelMaterialsCommon',[
'../Core/defaultValue',
'../Core/defined',
'../Core/WebGLConstants'
], function(
defaultValue,
defined,
WebGLConstants) {
'use strict';
function webGLConstantToGlslType(webGLValue) {
switch(webGLValue) {
case WebGLConstants.FLOAT:
return 'float';
case WebGLConstants.FLOAT_VEC2:
return 'vec2';
case WebGLConstants.FLOAT_VEC3:
return 'vec3';
case WebGLConstants.FLOAT_VEC4:
return 'vec4';
case WebGLConstants.FLOAT_MAT2:
return 'mat2';
case WebGLConstants.FLOAT_MAT3:
return 'mat3';
case WebGLConstants.FLOAT_MAT4:
return 'mat4';
case WebGLConstants.SAMPLER_2D:
return 'sampler2D';
}
}
function generateLightParameters(gltf) {
var result = {};
var lights;
if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) {
lights = gltf.extensions.KHR_materials_common.lights;
}
if (defined(lights)) {
// Figure out which node references the light
var nodes = gltf.nodes;
for (var nodeName in nodes) {
if (nodes.hasOwnProperty(nodeName)) {
var node = nodes[nodeName];
if (defined(node.extensions) && defined(node.extensions.KHR_materials_common)) {
var nodeLightId = node.extensions.KHR_materials_common.light;
if (defined(nodeLightId) && defined(lights[nodeLightId])) {
lights[nodeLightId].node = nodeName;
}
delete node.extensions.KHR_materials_common;
}
}
}
// Add light parameters to result
var lightCount = 0;
for(var lightName in lights) {
if (lights.hasOwnProperty(lightName)) {
var light = lights[lightName];
var lightType = light.type;
if ((lightType !== 'ambient') && !defined(light.node)) {
delete lights[lightName];
continue;
}
var lightBaseName = 'light' + lightCount.toString();
light.baseName = lightBaseName;
switch(lightType) {
case 'ambient':
var ambient = light.ambient;
result[lightBaseName + 'Color'] = {
type: WebGLConstants.FLOAT_VEC3,
value: ambient.color
};
break;
case 'directional':
var directional = light.directional;
result[lightBaseName + 'Color'] =
{
type: WebGLConstants.FLOAT_VEC3,
value: directional.color
};
if (defined(light.node)) {
result[lightBaseName + 'Transform'] =
{
node: light.node,
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
};
}
break;
case 'point':
var point = light.point;
result[lightBaseName + 'Color'] =
{
type: WebGLConstants.FLOAT_VEC3,
value: point.color
};
if (defined(light.node)) {
result[lightBaseName + 'Transform'] =
{
node: light.node,
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
};
}
result[lightBaseName + 'Attenuation'] =
{
type: WebGLConstants.FLOAT_VEC3,
value: [point.constantAttenuation, point.linearAttenuation, point.quadraticAttenuation]
};
break;
case 'spot':
var spot = light.spot;
result[lightBaseName + 'Color'] =
{
type: WebGLConstants.FLOAT_VEC3,
value: spot.color
};
if (defined(light.node)) {
result[lightBaseName + 'Transform'] =
{
node: light.node,
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
};
result[lightBaseName + 'InverseTransform'] = {
node: light.node,
semantic: 'MODELVIEWINVERSE',
type: WebGLConstants.FLOAT_MAT4,
useInFragment: true
};
}
result[lightBaseName + 'Attenuation'] =
{
type: WebGLConstants.FLOAT_VEC3,
value: [spot.constantAttenuation, spot.linearAttenuation, spot.quadraticAttenuation]
};
result[lightBaseName + 'FallOff'] =
{
type: WebGLConstants.FLOAT_VEC2,
value: [spot.fallOffAngle, spot.fallOffExponent]
};
break;
}
++lightCount;
}
}
}
return result;
}
function getNextId(dictionary, baseName, startingCount) {
var count = defaultValue(startingCount, 0);
var nextId;
do {
nextId = baseName + (count++).toString();
} while(defined(dictionary[nextId]));
return nextId;
}
var techniqueCount = 0;
var vertexShaderCount = 0;
var fragmentShaderCount = 0;
var programCount = 0;
function generateTechnique(gltf, khrMaterialsCommon, lightParameters) {
var techniques = gltf.techniques;
var shaders = gltf.shaders;
var programs = gltf.programs;
var lightingModel = khrMaterialsCommon.technique.toUpperCase();
var lights;
if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) {
lights = gltf.extensions.KHR_materials_common.lights;
}
var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0);
var hasSkinning = (jointCount > 0);
var parameterValues = khrMaterialsCommon.values;
var vertexShader = 'precision highp float;\n';
var fragmentShader = 'precision highp float;\n';
// Generate IDs for our new objects
var techniqueId = getNextId(techniques, 'technique', techniqueCount);
var vertexShaderId = getNextId(shaders, 'vertexShader', vertexShaderCount);
var fragmentShaderId = getNextId(shaders, 'fragmentShader', fragmentShaderCount);
var programId = getNextId(programs, 'program', programCount);
var hasNormals = (lightingModel !== 'CONSTANT');
// Add techniques
var techniqueParameters = {
// Add matrices
modelViewMatrix: {
semantic: 'MODELVIEW',
type: WebGLConstants.FLOAT_MAT4
},
projectionMatrix: {
semantic: 'PROJECTION',
type: WebGLConstants.FLOAT_MAT4
}
};
if (hasNormals) {
techniqueParameters.normalMatrix = {
semantic: 'MODELVIEWINVERSETRANSPOSE',
type: WebGLConstants.FLOAT_MAT3
};
}
if (hasSkinning) {
techniqueParameters.jointMatrix = {
count: jointCount,
semantic: 'JOINTMATRIX',
type: WebGLConstants.FLOAT_MAT4
};
}
// Add material parameters
var lowerCase;
var hasTexCoords = false;
for(var name in parameterValues) {
//generate shader parameters for KHR_materials_common attributes
//(including a check, because some boolean flags should not be used as shader parameters)
if (parameterValues.hasOwnProperty(name) && (name !== 'transparent') && (name !== 'doubleSided')) {
var valType = getKHRMaterialsCommonValueType(name, parameterValues[name]);
lowerCase = name.toLowerCase();
if (!hasTexCoords && (valType === WebGLConstants.SAMPLER_2D)) {
hasTexCoords = true;
}
techniqueParameters[lowerCase] = {
type: valType
};
}
}
// Copy light parameters into technique parameters
if (defined(lightParameters)) {
for (var lightParamName in lightParameters) {
if (lightParameters.hasOwnProperty(lightParamName)) {
techniqueParameters[lightParamName] = lightParameters[lightParamName];
}
}
}
// Generate uniforms object before attributes are added
var techniqueUniforms = {};
for (var paramName in techniqueParameters) {
if (techniqueParameters.hasOwnProperty(paramName)) {
var param = techniqueParameters[paramName];
techniqueUniforms['u_' + paramName] = paramName;
var arraySize = defined(param.count) ? '['+param.count+']' : '';
if (((param.type !== WebGLConstants.FLOAT_MAT3) && (param.type !== WebGLConstants.FLOAT_MAT4)) ||
param.useInFragment) {
fragmentShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n';
delete param.useInFragment;
}
else {
vertexShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n';
}
}
}
// Add attributes with semantics
var vertexShaderMain = '';
if (hasSkinning) {
vertexShaderMain += ' mat4 skinMat = a_weight.x * u_jointMatrix[int(a_joint.x)];\n';
vertexShaderMain += ' skinMat += a_weight.y * u_jointMatrix[int(a_joint.y)];\n';
vertexShaderMain += ' skinMat += a_weight.z * u_jointMatrix[int(a_joint.z)];\n';
vertexShaderMain += ' skinMat += a_weight.w * u_jointMatrix[int(a_joint.w)];\n';
}
// Add position always
var techniqueAttributes = {
a_position: 'position'
};
techniqueParameters.position = {
semantic: 'POSITION',
type: WebGLConstants.FLOAT_VEC3
};
vertexShader += 'attribute vec3 a_position;\n';
vertexShader += 'varying vec3 v_positionEC;\n';
if (hasSkinning) {
vertexShaderMain += ' vec4 pos = u_modelViewMatrix * skinMat * vec4(a_position,1.0);\n';
}
else {
vertexShaderMain += ' vec4 pos = u_modelViewMatrix * vec4(a_position,1.0);\n';
}
vertexShaderMain += ' v_positionEC = pos.xyz;\n';
vertexShaderMain += ' gl_Position = u_projectionMatrix * pos;\n';
fragmentShader += 'varying vec3 v_positionEC;\n';
// Add normal if we don't have constant lighting
if (hasNormals) {
techniqueAttributes.a_normal = 'normal';
techniqueParameters.normal = {
semantic: 'NORMAL',
type: WebGLConstants.FLOAT_VEC3
};
vertexShader += 'attribute vec3 a_normal;\n';
vertexShader += 'varying vec3 v_normal;\n';
if (hasSkinning) {
vertexShaderMain += ' v_normal = u_normalMatrix * mat3(skinMat) * a_normal;\n';
}
else {
vertexShaderMain += ' v_normal = u_normalMatrix * a_normal;\n';
}
fragmentShader += 'varying vec3 v_normal;\n';
}
// Add texture coordinates if the material uses them
var v_texcoord;
if (hasTexCoords) {
techniqueAttributes.a_texcoord_0 = 'texcoord_0';
techniqueParameters.texcoord_0 = {
semantic: 'TEXCOORD_0',
type: WebGLConstants.FLOAT_VEC2
};
v_texcoord = 'v_texcoord_0';
vertexShader += 'attribute vec2 a_texcoord_0;\n';
vertexShader += 'varying vec2 ' + v_texcoord + ';\n';
vertexShaderMain += ' ' + v_texcoord + ' = a_texcoord_0;\n';
fragmentShader += 'varying vec2 ' + v_texcoord + ';\n';
}
if (hasSkinning) {
techniqueAttributes.a_joint = 'joint';
techniqueParameters.joint = {
semantic: 'JOINT',
type: WebGLConstants.FLOAT_VEC4
};
techniqueAttributes.a_weight = 'weight';
techniqueParameters.weight = {
semantic: 'WEIGHT',
type: WebGLConstants.FLOAT_VEC4
};
vertexShader += 'attribute vec4 a_joint;\n';
vertexShader += 'attribute vec4 a_weight;\n';
}
var hasSpecular = hasNormals && ((lightingModel === 'BLINN') || (lightingModel === 'PHONG')) &&
defined(techniqueParameters.specular) && defined(techniqueParameters.shininess);
// Generate lighting code blocks
var hasNonAmbientLights = false;
var hasAmbientLights = false;
var fragmentLightingBlock = '';
for (var lightName in lights) {
if (lights.hasOwnProperty(lightName)) {
var light = lights[lightName];
var lightType = light.type.toLowerCase();
var lightBaseName = light.baseName;
fragmentLightingBlock += ' {\n';
var lightColorName = 'u_' + lightBaseName + 'Color';
var varyingDirectionName;
var varyingPositionName;
if(lightType === 'ambient') {
hasAmbientLights = true;
fragmentLightingBlock += ' ambientLight += ' + lightColorName + ';\n';
}
else if (hasNormals) {
hasNonAmbientLights = true;
varyingDirectionName = 'v_' + lightBaseName + 'Direction';
varyingPositionName = 'v_' + lightBaseName + 'Position';
if (lightType !== 'point') {
vertexShader += 'varying vec3 ' + varyingDirectionName + ';\n';
fragmentShader += 'varying vec3 ' + varyingDirectionName + ';\n';
vertexShaderMain += ' ' + varyingDirectionName + ' = mat3(u_' + lightBaseName + 'Transform) * vec3(0.,0.,1.);\n';
if (lightType === 'directional') {
fragmentLightingBlock += ' vec3 l = normalize(' + varyingDirectionName + ');\n';
}
}
if (lightType !== 'directional') {
vertexShader += 'varying vec3 ' + varyingPositionName + ';\n';
fragmentShader += 'varying vec3 ' + varyingPositionName + ';\n';
vertexShaderMain += ' ' + varyingPositionName + ' = u_' + lightBaseName + 'Transform[3].xyz;\n';
fragmentLightingBlock += ' vec3 VP = ' + varyingPositionName + ' - v_positionEC;\n';
fragmentLightingBlock += ' vec3 l = normalize(VP);\n';
fragmentLightingBlock += ' float range = length(VP);\n';
fragmentLightingBlock += ' float attenuation = 1.0 / (u_' + lightBaseName + 'Attenuation.x + ';
fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.y * range) + ';
fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.z * range * range));\n';
}
else {
fragmentLightingBlock += ' float attenuation = 1.0;\n';
}
if (lightType === 'spot') {
fragmentLightingBlock += ' float spotDot = dot(l, normalize(' + varyingDirectionName + '));\n';
fragmentLightingBlock += ' if (spotDot < cos(u_' + lightBaseName + 'FallOff.x * 0.5))\n';
fragmentLightingBlock += ' {\n';
fragmentLightingBlock += ' attenuation = 0.0;\n';
fragmentLightingBlock += ' }\n';
fragmentLightingBlock += ' else\n';
fragmentLightingBlock += ' {\n';
fragmentLightingBlock += ' attenuation *= max(0.0, pow(spotDot, u_' + lightBaseName + 'FallOff.y));\n';
fragmentLightingBlock += ' }\n';
}
fragmentLightingBlock += ' diffuseLight += ' + lightColorName + '* max(dot(normal,l), 0.) * attenuation;\n';
if (hasSpecular) {
if (lightingModel === 'BLINN') {
fragmentLightingBlock += ' vec3 h = normalize(l + viewDir);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess)) * attenuation;\n';
}
else { // PHONG
fragmentLightingBlock += ' vec3 reflectDir = reflect(-l, normal);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess)) * attenuation;\n';
}
fragmentLightingBlock += ' specularLight += ' + lightColorName + ' * specularIntensity;\n';
}
}
fragmentLightingBlock += ' }\n';
}
}
if (!hasAmbientLights) {
// Add an ambient light if we don't have one
fragmentLightingBlock += ' ambientLight += vec3(0.2, 0.2, 0.2);\n';
}
if (!hasNonAmbientLights && (lightingModel !== 'CONSTANT')) {
fragmentLightingBlock += ' vec3 l = normalize(czm_sunDirectionEC);\n';
fragmentLightingBlock += ' diffuseLight += vec3(1.0, 1.0, 1.0) * max(dot(normal,l), 0.);\n';
if (hasSpecular) {
if (lightingModel === 'BLINN') {
fragmentLightingBlock += ' vec3 h = normalize(l + viewDir);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess));\n';
}
else { // PHONG
fragmentLightingBlock += ' vec3 reflectDir = reflect(-l, normal);\n';
fragmentLightingBlock += ' float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess));\n';
}
fragmentLightingBlock += ' specularLight += vec3(1.0, 1.0, 1.0) * specularIntensity;\n';
}
}
vertexShader += 'void main(void) {\n';
vertexShader += vertexShaderMain;
vertexShader += '}\n';
fragmentShader += 'void main(void) {\n';
var colorCreationBlock = ' vec3 color = vec3(0.0, 0.0, 0.0);\n';
if (hasNormals) {
fragmentShader += ' vec3 normal = normalize(v_normal);\n';
if (khrMaterialsCommon.doubleSided) {
fragmentShader += ' if (gl_FrontFacing == false)\n';
fragmentShader += ' {\n';
fragmentShader += ' normal = -normal;\n';
fragmentShader += ' }\n';
}
}
var finalColorComputation;
if (lightingModel !== 'CONSTANT') {
if (defined(techniqueParameters.diffuse)) {
if (techniqueParameters.diffuse.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec4 diffuse = texture2D(u_diffuse, ' + v_texcoord + ');\n';
}
else {
fragmentShader += ' vec4 diffuse = u_diffuse;\n';
}
fragmentShader += ' vec3 diffuseLight = vec3(0.0, 0.0, 0.0);\n';
colorCreationBlock += ' color += diffuse.rgb * diffuseLight;\n';
}
if (hasSpecular) {
if (techniqueParameters.specular.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec3 specular = texture2D(u_specular, ' + v_texcoord + ').rgb;\n';
}
else {
fragmentShader += ' vec3 specular = u_specular.rgb;\n';
}
fragmentShader += ' vec3 specularLight = vec3(0.0, 0.0, 0.0);\n';
colorCreationBlock += ' color += specular * specularLight;\n';
}
if (defined(techniqueParameters.transparency)) {
finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a, diffuse.a * u_transparency);\n';
}
else {
finalColorComputation = ' gl_FragColor = vec4(color * diffuse.a, diffuse.a);\n';
}
}
else {
if (defined(techniqueParameters.transparency)) {
finalColorComputation = ' gl_FragColor = vec4(color, u_transparency);\n';
}
else {
finalColorComputation = ' gl_FragColor = vec4(color, 1.0);\n';
}
}
if (defined(techniqueParameters.emission)) {
if (techniqueParameters.emission.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec3 emission = texture2D(u_emission, ' + v_texcoord + ').rgb;\n';
}
else {
fragmentShader += ' vec3 emission = u_emission.rgb;\n';
}
colorCreationBlock += ' color += emission;\n';
}
if (defined(techniqueParameters.ambient) || (lightingModel !== 'CONSTANT')) {
if (defined(techniqueParameters.ambient)) {
if (techniqueParameters.ambient.type === WebGLConstants.SAMPLER_2D) {
fragmentShader += ' vec3 ambient = texture2D(u_ambient, ' + v_texcoord + ').rgb;\n';
}
else {
fragmentShader += ' vec3 ambient = u_ambient.rgb;\n';
}
}
else {
fragmentShader += ' vec3 ambient = diffuse.rgb;\n';
}
colorCreationBlock += ' color += ambient * ambientLight;\n';
}
fragmentShader += ' vec3 viewDir = -normalize(v_positionEC);\n';
fragmentShader += ' vec3 ambientLight = vec3(0.0, 0.0, 0.0);\n';
// Add in light computations
fragmentShader += fragmentLightingBlock;
fragmentShader += colorCreationBlock;
fragmentShader += finalColorComputation;
fragmentShader += '}\n';
var techniqueStates;
if (khrMaterialsCommon.transparent) {
techniqueStates = {
enable: [
WebGLConstants.DEPTH_TEST,
WebGLConstants.BLEND
],
depthMask: false,
functions: {
blendEquationSeparate: [
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD
],
blendFuncSeparate: [
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA,
WebGLConstants.ONE,
WebGLConstants.ONE_MINUS_SRC_ALPHA
]
}
};
}
else if (khrMaterialsCommon.doubleSided) {
techniqueStates = {
enable: [
WebGLConstants.DEPTH_TEST
]
};
}
else { // Not transparent or double sided
techniqueStates = {
enable: [
WebGLConstants.CULL_FACE,
WebGLConstants.DEPTH_TEST
]
};
}
techniques[techniqueId] = {
attributes: techniqueAttributes,
parameters: techniqueParameters,
program: programId,
states: techniqueStates,
uniforms: techniqueUniforms
};
// Add shaders
shaders[vertexShaderId] = {
type: WebGLConstants.VERTEX_SHADER,
uri: '',
extras: {
source: vertexShader
}
};
shaders[fragmentShaderId] = {
type: WebGLConstants.FRAGMENT_SHADER,
uri: '',
extras: {
source: fragmentShader
}
};
// Add program
var programAttributes = Object.keys(techniqueAttributes);
programs[programId] = {
attributes: programAttributes,
fragmentShader: fragmentShaderId,
vertexShader: vertexShaderId
};
return techniqueId;
}
function getKHRMaterialsCommonValueType(paramName, paramValue)
{
var value;
// Backwards compatibility for COLLADA2GLTF v1.0-draft when it encoding
// materials using KHR_materials_common with explicit type/value members
if (defined(paramValue.value)) {
value = paramValue.value;
} else {
value = paramValue;
}
switch (paramName) {
case 'ambient':
return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'diffuse':
return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'emission':
return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'specular':
return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
case 'shininess':
return WebGLConstants.FLOAT;
case 'transparency':
return WebGLConstants.FLOAT;
// these two are usually not used directly within shaders,
// they are just added here for completeness
case 'transparent':
return WebGLConstants.BOOL;
case 'doubleSided':
return WebGLConstants.BOOL;
}
}
function getTechniqueKey(khrMaterialsCommon) {
var techniqueKey = '';
techniqueKey += 'technique:' + khrMaterialsCommon.technique + ';';
var values = khrMaterialsCommon.values;
var keys = Object.keys(values).sort();
var keysCount = keys.length;
for (var i=0;i
* Use {@link Model#getMesh} to create an instance.
*
*
* @alias ModelMesh
* @internalConstructor
*
* @see Model#getMesh
*/
function ModelMesh(mesh, runtimeMaterialsById, id) {
var materials = [];
var primitives = mesh.primitives;
var length = primitives.length;
for (var i = 0; i < length; ++i) {
var p = primitives[i];
materials[i] = runtimeMaterialsById[p.material];
}
this._name = mesh.name;
this._materials = materials;
this._id = id;
}
defineProperties(ModelMesh.prototype, {
/**
* The value of the name
property of this mesh. This is the
* name assigned by the artist when the asset is created. This can be
* different than the name of the mesh property ({@link ModelMesh#id}),
* which is internal to glTF.
*
* @memberof ModelMesh.prototype
*
* @type {String}
* @readonly
*/
name : {
get : function() {
return this._name;
}
},
/**
* The name of the glTF JSON property for this mesh. This is guaranteed
* to be unique among all meshes. It may not match the mesh's
* name
property (@link ModelMesh#name), which is assigned by
* the artist when the asset is created.
*
* @memberof ModelMesh.prototype
*
* @type {String}
* @readonly
*/
id : {
get : function() {
return this._id;
}
},
/**
* An array of {@link ModelMaterial} instances indexed by the mesh's
* primitive indices.
*
* @memberof ModelMesh.prototype
*
* @type {ModelMaterial[]}
* @readonly
*/
materials : {
get : function() {
return this._materials;
}
}
});
return ModelMesh;
});
/*global define*/
define('Scene/ModelNode',[
'../Core/defineProperties',
'../Core/Matrix4'
], function(
defineProperties,
Matrix4) {
'use strict';
/**
* A model node with a transform for user-defined animations. A glTF asset can
* contain animations that target a node's transform. This class allows
* changing a node's transform externally so animation can be driven by another
* source, not just an animation in the glTF asset.
*
* Use {@link Model#getNode} to create an instance.
*
*
* @alias ModelNode
* @internalConstructor
*
*
* @example
* var node = model.getNode('LOD3sp');
* node.matrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(5.0, 1.0, 1.0), node.matrix);
*
* @see Model#getNode
*/
function ModelNode(model, node, runtimeNode, id, matrix) {
this._model = model;
this._runtimeNode = runtimeNode;
this._name = node.name;
this._id = id;
/**
* @private
*/
this.useMatrix = false;
this._show = true;
this._matrix = Matrix4.clone(matrix);
}
defineProperties(ModelNode.prototype, {
/**
* The value of the name
property of this node. This is the
* name assigned by the artist when the asset is created. This can be
* different than the name of the node property ({@link ModelNode#id}),
* which is internal to glTF.
*
* @memberof ModelNode.prototype
*
* @type {String}
* @readonly
*/
name : {
get : function() {
return this._name;
}
},
/**
* The name of the glTF JSON property for this node. This is guaranteed
* to be unique among all nodes. It may not match the node's
* name
property (@link ModelNode#name), which is assigned by
* the artist when the asset is created.
*
* @memberof ModelNode.prototype
*
* @type {String}
* @readonly
*/
id : {
get : function() {
return this._id;
}
},
/**
* Determines if this node and its children will be shown.
*
* @memberof ModelNode.prototype
* @type {Boolean}
*
* @default true
*/
show : {
get : function() {
return this._show;
},
set : function(value) {
if (this._show !== value) {
this._show = value;
this._model._perNodeShowDirty = true;
}
}
},
/**
* The node's 4x4 matrix transform from its local coordinates to
* its parent's.
*
* For changes to take effect, this property must be assigned to;
* setting individual elements of the matrix will not work.
*
*
* @memberof ModelNode.prototype
* @type {Matrix4}
*/
matrix : {
get : function() {
return this._matrix;
},
set : function(value) {
this._matrix = Matrix4.clone(value, this._matrix);
this.useMatrix = true;
var model = this._model;
model._cesiumAnimationsDirty = true;
this._runtimeNode.dirtyNumber = model._maxDirtyNumber;
}
}
});
/**
* @private
*/
ModelNode.prototype.setMatrix = function(matrix) {
// Update matrix but do not set the dirty flag since this is used internally
// to keep the matrix in-sync during a glTF animation.
Matrix4.clone(matrix, this._matrix);
};
return ModelNode;
});
/*global define*/
define('Scene/Model',[
'../Core/BoundingSphere',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/clone',
'../Core/Color',
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/FeatureDetection',
'../Core/getAbsoluteUri',
'../Core/getBaseUri',
'../Core/getMagic',
'../Core/getStringFromTypedArray',
'../Core/IndexDatatype',
'../Core/loadArrayBuffer',
'../Core/loadImage',
'../Core/loadImageFromTypedArray',
'../Core/loadText',
'../Core/Math',
'../Core/Matrix2',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/PrimitiveType',
'../Core/Quaternion',
'../Core/Queue',
'../Core/RuntimeError',
'../Core/Transforms',
'../Core/WebGLConstants',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/Sampler',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/Texture',
'../Renderer/TextureMinificationFilter',
'../Renderer/TextureWrap',
'../Renderer/VertexArray',
'../ThirdParty/gltfDefaults',
'../ThirdParty/Uri',
'../ThirdParty/when',
'./BlendingState',
'./ColorBlendMode',
'./getAttributeOrUniformBySemantic',
'./getBinaryAccessor',
'./HeightReference',
'./ModelAnimationCache',
'./ModelAnimationCollection',
'./ModelMaterial',
'./modelMaterialsCommon',
'./ModelMesh',
'./ModelNode',
'./SceneMode',
'./ShadowMode'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
clone,
Color,
combine,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
FeatureDetection,
getAbsoluteUri,
getBaseUri,
getMagic,
getStringFromTypedArray,
IndexDatatype,
loadArrayBuffer,
loadImage,
loadImageFromTypedArray,
loadText,
CesiumMath,
Matrix2,
Matrix3,
Matrix4,
PrimitiveType,
Quaternion,
Queue,
RuntimeError,
Transforms,
WebGLConstants,
Buffer,
BufferUsage,
DrawCommand,
Pass,
RenderState,
Sampler,
ShaderProgram,
ShaderSource,
Texture,
TextureMinificationFilter,
TextureWrap,
VertexArray,
gltfDefaults,
Uri,
when,
BlendingState,
ColorBlendMode,
getAttributeOrUniformBySemantic,
getBinaryAccessor,
HeightReference,
ModelAnimationCache,
ModelAnimationCollection,
ModelMaterial,
modelMaterialsCommon,
ModelMesh,
ModelNode,
SceneMode,
ShadowMode) {
'use strict';
// Bail out if the browser doesn't support typed arrays, to prevent the setup function
// from failing, since we won't be able to create a WebGL context anyway.
if (!FeatureDetection.supportsTypedArrays()) {
return {};
}
var yUpToZUp = Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO));
var boundingSphereCartesian3Scratch = new Cartesian3();
var ModelState = {
NEEDS_LOAD : 0,
LOADING : 1,
LOADED : 2, // Renderable, but textures can still be pending when incrementallyLoadTextures is true.
FAILED : 3
};
// GLTF_SPEC: Figure out correct mime types (https://github.com/KhronosGroup/glTF/issues/412)
var defaultModelAccept = 'model/vnd.gltf.binary,model/vnd.gltf+json,model/gltf.binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01';
function LoadResources() {
this.buffersToCreate = new Queue();
this.buffers = {};
this.pendingBufferLoads = 0;
this.programsToCreate = new Queue();
this.shaders = {};
this.pendingShaderLoads = 0;
this.texturesToCreate = new Queue();
this.pendingTextureLoads = 0;
this.texturesToCreateFromBufferView = new Queue();
this.pendingBufferViewToImage = 0;
this.createSamplers = true;
this.createSkins = true;
this.createRuntimeAnimations = true;
this.createVertexArrays = true;
this.createRenderStates = true;
this.createUniformMaps = true;
this.createRuntimeNodes = true;
this.skinnedNodesIds = [];
}
LoadResources.prototype.getBuffer = function(bufferView) {
return getSubarray(this.buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength);
};
LoadResources.prototype.finishedPendingBufferLoads = function() {
return (this.pendingBufferLoads === 0);
};
LoadResources.prototype.finishedBuffersCreation = function() {
return ((this.pendingBufferLoads === 0) && (this.buffersToCreate.length === 0));
};
LoadResources.prototype.finishedProgramCreation = function() {
return ((this.pendingShaderLoads === 0) && (this.programsToCreate.length === 0));
};
LoadResources.prototype.finishedTextureCreation = function() {
var finishedPendingLoads = (this.pendingTextureLoads === 0);
var finishedResourceCreation =
(this.texturesToCreate.length === 0) &&
(this.texturesToCreateFromBufferView.length === 0);
return finishedPendingLoads && finishedResourceCreation;
};
LoadResources.prototype.finishedEverythingButTextureCreation = function() {
var finishedPendingLoads =
(this.pendingBufferLoads === 0) &&
(this.pendingShaderLoads === 0);
var finishedResourceCreation =
(this.buffersToCreate.length === 0) &&
(this.programsToCreate.length === 0) &&
(this.pendingBufferViewToImage === 0);
return finishedPendingLoads && finishedResourceCreation;
};
LoadResources.prototype.finished = function() {
return this.finishedTextureCreation() && this.finishedEverythingButTextureCreation();
};
///////////////////////////////////////////////////////////////////////////
function setCachedGltf(model, cachedGltf) {
model._cachedGltf = cachedGltf;
model._animationIds = getAnimationIds(cachedGltf);
}
// glTF JSON can be big given embedded geometry, textures, and animations, so we
// cache it across all models using the same url/cache-key. This also reduces the
// slight overhead in assigning defaults to missing values.
//
// Note that this is a global cache, compared to renderer resources, which
// are cached per context.
function CachedGltf(options) {
this._gltf = modelMaterialsCommon(gltfDefaults(options.gltf));
this._bgltf = options.bgltf;
this.ready = options.ready;
this.modelsToLoad = [];
this.count = 0;
}
defineProperties(CachedGltf.prototype, {
gltf : {
set : function(value) {
this._gltf = modelMaterialsCommon(gltfDefaults(value));
},
get : function() {
return this._gltf;
}
},
bgltf : {
get : function() {
return this._bgltf;
}
}
});
CachedGltf.prototype.makeReady = function(gltfJson, bgltf) {
this.gltf = gltfJson;
this._bgltf = bgltf;
var models = this.modelsToLoad;
var length = models.length;
for (var i = 0; i < length; ++i) {
var m = models[i];
if (!m.isDestroyed()) {
setCachedGltf(m, this);
}
}
this.modelsToLoad = undefined;
this.ready = true;
};
function getAnimationIds(cachedGltf) {
var animationIds = [];
if (defined(cachedGltf) && defined(cachedGltf.gltf)) {
var animations = cachedGltf.gltf.animations;
for (var id in animations) {
if (animations.hasOwnProperty(id)) {
animationIds.push(id);
}
}
}
return animationIds;
}
var gltfCache = {};
///////////////////////////////////////////////////////////////////////////
/**
* A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
*
* Cesium includes support for geometry and materials, glTF animations, and glTF skinning.
* In addition, individual glTF nodes are pickable with {@link Scene#pick} and animatable
* with {@link Model#getNode}. glTF cameras and lights are not currently supported.
*
*
* An external glTF asset is created with {@link Model.fromGltf}. glTF JSON can also be
* created at runtime and passed to this constructor function. In either case, the
* {@link Model#readyPromise} is resolved when the model is ready to render, i.e.,
* when the external binary, image, and shader files are downloaded and the WebGL
* resources are created.
*
*
* For high-precision rendering, Cesium supports the CESIUM_RTC extension, which introduces the
* CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated
* relative to a local origin.
*
*
* @alias Model
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Object|ArrayBuffer|Uint8Array} [options.gltf] The object for the glTF JSON or an arraybuffer of Binary glTF defined by the KHR_binary_glTF extension.
* @param {String} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
* @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
* @param {Number} [options.scale=1.0] A uniform scale applied to this model.
* @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
* @param {Number} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
* @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
* @param {Boolean} [options.allowPicking=true] When true
, each glTF mesh and primitive is pickable with {@link Scene#pick}.
* @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
* @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
* @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from each light source.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
* @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
* @param {HeightReference} [options.heightReference] Determines how the model is drawn relative to terrain.
* @param {Scene} [options.scene] Must be passed in for models that use the height reference property.
* @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed.
* @param {Color} [options.color=Color.WHITE] A color that blends with the model's rendered color.
* @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
* @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the colorBlendMode
is MIX
. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
* @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts.
* @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels.
*
* @exception {DeveloperError} bgltf is not a valid Binary glTF file.
* @exception {DeveloperError} Only glTF Binary version 1 is supported.
*
* @see Model.fromGltf
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo}
*/
function Model(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var cacheKey = options.cacheKey;
this._cacheKey = cacheKey;
this._cachedGltf = undefined;
this._releaseGltfJson = defaultValue(options.releaseGltfJson, false);
this._animationIds = undefined;
var cachedGltf;
if (defined(cacheKey) && defined(gltfCache[cacheKey]) && gltfCache[cacheKey].ready) {
// glTF JSON is in cache and ready
cachedGltf = gltfCache[cacheKey];
++cachedGltf.count;
} else {
// glTF was explicitly provided, e.g., when a user uses the Model constructor directly
var gltf = options.gltf;
if (defined(gltf)) {
if (gltf instanceof ArrayBuffer) {
gltf = new Uint8Array(gltf);
}
if (gltf instanceof Uint8Array) {
// Binary glTF
var result = parseBinaryGltfHeader(gltf);
// KHR_binary_glTF is from the beginning of the binary section
if (result.binaryOffset !== 0) {
gltf = gltf.subarray(result.binaryOffset);
}
cachedGltf = new CachedGltf({
gltf : result.glTF,
bgltf : gltf,
ready : true
});
} else {
// Normal glTF (JSON)
cachedGltf = new CachedGltf({
gltf : options.gltf,
ready : true
});
}
cachedGltf.count = 1;
if (defined(cacheKey)) {
gltfCache[cacheKey] = cachedGltf;
}
}
}
setCachedGltf(this, cachedGltf);
this._basePath = defaultValue(options.basePath, '');
var docUri = new Uri(document.location.href);
var modelUri = new Uri(this._basePath);
this._baseUri = modelUri.resolve(docUri);
/**
* Determines if the model primitive will be shown.
*
* @type {Boolean}
*
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* The silhouette color.
*
* @type {Color}
*
* @default Color.RED
*/
this.silhouetteColor = defaultValue(options.silhouetteColor, Color.RED);
this._silhouetteColor = new Color();
this._normalAttributeName = undefined;
/**
* The size of the silhouette in pixels.
*
* @type {Number}
*
* @default 0.0
*/
this.silhouetteSize = defaultValue(options.silhouetteSize, 0.0);
/**
* The 4x4 transformation matrix that transforms the model from model to world coordinates.
* When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
*
* @default {@link Matrix4.IDENTITY}
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = Matrix4.clone(this.modelMatrix);
this._clampedModelMatrix = undefined;
/**
* A uniform scale applied to this model before the {@link Model#modelMatrix}.
* Values greater than 1.0
increase the size of the model; values
* less than 1.0
decrease.
*
* @type {Number}
*
* @default 1.0
*/
this.scale = defaultValue(options.scale, 1.0);
this._scale = this.scale;
/**
* The approximate minimum pixel size of the model regardless of zoom.
* This can be used to ensure that a model is visible even when the viewer
* zooms out. When 0.0
, no minimum size is enforced.
*
* @type {Number}
*
* @default 0.0
*/
this.minimumPixelSize = defaultValue(options.minimumPixelSize, 0.0);
this._minimumPixelSize = this.minimumPixelSize;
/**
* The maximum scale size for a model. This can be used to give
* an upper limit to the {@link Model#minimumPixelSize}, ensuring that the model
* is never an unreasonable scale.
*
* @type {Number}
*/
this.maximumScale = options.maximumScale;
this._maximumScale = this.maximumScale;
/**
* User-defined object returned when the model is picked.
*
* @type Object
*
* @default undefined
*
* @see Scene#pick
*/
this.id = options.id;
this._id = options.id;
/**
* Returns the height reference of the model
*
* @memberof Model.prototype
*
* @type {HeightReference}
*
* @default HeightReference.NONE
*/
this.heightReference = defaultValue(options.heightReference, HeightReference.NONE);
this._heightReference = this.heightReference;
this._heightChanged = false;
this._removeUpdateHeightCallback = undefined;
var scene = options.scene;
this._scene = scene;
if (defined(scene)) {
scene.terrainProviderChanged.addEventListener(function() {
this._heightChanged = true;
}, this);
}
/**
* Used for picking primitives that wrap a model.
*
* @private
*/
this.pickPrimitive = options.pickPrimitive;
this._allowPicking = defaultValue(options.allowPicking, true);
this._ready = false;
this._readyPromise = when.defer();
/**
* The currently playing glTF animations.
*
* @type {ModelAnimationCollection}
*/
this.activeAnimations = new ModelAnimationCollection(this);
this._defaultTexture = undefined;
this._incrementallyLoadTextures = defaultValue(options.incrementallyLoadTextures, true);
this._asynchronous = defaultValue(options.asynchronous, true);
/**
* Determines whether the model casts or receives shadows from each light source.
*
* @type {ShadowMode}
*
* @default ShadowMode.ENABLED
*/
this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
this._shadows = this.shadows;
/**
* A color that blends with the model's rendered color.
*
* @type {Color}
*
* @default Color.WHITE
*/
this.color = defaultValue(options.color, Color.WHITE);
this._color = new Color();
/**
* Defines how the color blends with the model.
*
* @type {ColorBlendMode}
*
* @default ColorBlendMode.HIGHLIGHT
*/
this.colorBlendMode = defaultValue(options.colorBlendMode, ColorBlendMode.HIGHLIGHT);
/**
* Value used to determine the color strength when the colorBlendMode
is MIX
.
* A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with
* any value in-between resulting in a mix of the two.
*
* @type {Number}
*
* @default 0.5
*/
this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the model. A glTF primitive corresponds
* to one draw command. A glTF mesh has an array of primitives, often of length one.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
this._debugShowBoundingVolume = false;
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the model in wireframe.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugWireframe = defaultValue(options.debugWireframe, false);
this._debugWireframe = false;
this._distanceDisplayCondition = options.distanceDisplayCondition;
// Undocumented options
this._precreatedAttributes = options.precreatedAttributes;
this._vertexShaderLoaded = options.vertexShaderLoaded;
this._fragmentShaderLoaded = options.fragmentShaderLoaded;
this._uniformMapLoaded = options.uniformMapLoaded;
this._pickVertexShaderLoaded = options.pickVertexShaderLoaded;
this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded;
this._pickUniformMapLoaded = options.pickUniformMapLoaded;
this._ignoreCommands = defaultValue(options.ignoreCommands, false);
/**
* @private
* @readonly
*/
this.cull = defaultValue(options.cull, true);
this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale
this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins
this._boundingSphere = undefined;
this._scaledBoundingSphere = new BoundingSphere();
this._state = ModelState.NEEDS_LOAD;
this._loadResources = undefined;
this._mode = undefined;
this._perNodeShowDirty = false; // true when the Cesium API was used to change a node's show property
this._cesiumAnimationsDirty = false; // true when the Cesium API, not a glTF animation, changed a node transform
this._dirty = false; // true when the model was transformed this frame
this._maxDirtyNumber = 0; // Used in place of a dirty boolean flag to avoid an extra graph traversal
this._runtime = {
animations : undefined,
rootNodes : undefined,
nodes : undefined, // Indexed with the node property's name, i.e., glTF id
nodesByName : undefined, // Indexed with name property in the node
skinnedNodes : undefined,
meshesByName : undefined, // Indexed with the name property in the mesh
materialsByName : undefined, // Indexed with the name property in the material
materialsById : undefined // Indexed with the material's property name
};
this._uniformMaps = {}; // Not cached since it can be targeted by glTF animation
this._extensionsUsed = undefined; // Cached used extensions in a hash-map so we don't have to search the gltf array
this._quantizedUniforms = {}; // Quantized uniforms for each program for WEB3D_quantized_attributes
this._programPrimitives = {};
this._rendererResources = { // Cached between models with the same url/cache-key
buffers : {},
vertexArrays : {},
programs : {},
pickPrograms : {},
silhouettePrograms: {},
textures : {},
samplers : {},
renderStates : {}
};
this._cachedRendererResources = undefined;
this._loadRendererResourcesFromCache = false;
this._nodeCommands = [];
this._pickIds = [];
// CESIUM_RTC extension
this._rtcCenter = undefined; // in world coordinates
this._rtcCenterEye = undefined; // in eye coordinates
}
defineProperties(Model.prototype, {
/**
* The object for the glTF JSON, including properties with default values omitted
* from the JSON provided to this model.
*
* @memberof Model.prototype
*
* @type {Object}
* @readonly
*
* @default undefined
*/
gltf : {
get : function() {
return defined(this._cachedGltf) ? this._cachedGltf.gltf : undefined;
}
},
/**
* When true
, the glTF JSON is not stored with the model once the model is
* loaded (when {@link Model#ready} is true
). This saves memory when
* geometry, textures, and animations are embedded in the .gltf file, which is the
* default for the {@link http://cesiumjs.org/convertmodel.html|Cesium model converter}.
* This is especially useful for cases like 3D buildings, where each .gltf model is unique
* and caching the glTF JSON is not effective.
*
* @memberof Model.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*
* @private
*/
releaseGltfJson : {
get : function() {
return this._releaseGltfJson;
}
},
/**
* The key identifying this model in the model cache for glTF JSON, renderer resources, and animations.
* Caching saves memory and improves loading speed when several models with the same url are created.
*
* This key is automatically generated when the model is created with {@link Model.fromGltf}. If the model
* is created directly from glTF JSON using the {@link Model} constructor, this key can be manually
* provided; otherwise, the model will not be changed.
*
*
* @memberof Model.prototype
*
* @type {String}
* @readonly
*
* @private
*/
cacheKey : {
get : function() {
return this._cacheKey;
}
},
/**
* The base path that paths in the glTF JSON are relative to. The base
* path is the same path as the path containing the .gltf file
* minus the .gltf file, when binary, image, and shader files are
* in the same directory as the .gltf. When this is ''
,
* the app's base path is used.
*
* @memberof Model.prototype
*
* @type {String}
* @readonly
*
* @default ''
*/
basePath : {
get : function() {
return this._basePath;
}
},
/**
* The model's bounding sphere in its local coordinate system. This does not take into
* account glTF animations and skins nor does it take into account {@link Model#minimumPixelSize}.
*
* @memberof Model.prototype
*
* @type {BoundingSphere}
* @readonly
*
* @default undefined
*
* @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true.
*
* @example
* // Center in WGS84 coordinates
* var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
*/
boundingSphere : {
get : function() {
if (this._state !== ModelState.LOADED) {
throw new DeveloperError('The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true.');
}
var modelMatrix = this.modelMatrix;
if ((this.heightReference !== HeightReference.NONE) && this._clampedModelMatrix) {
modelMatrix = this._clampedModelMatrix;
}
var nonUniformScale = Matrix4.getScale(modelMatrix, boundingSphereCartesian3Scratch);
var scale = defined(this.maximumScale) ? Math.min(this.maximumScale, this.scale) : this.scale;
Cartesian3.multiplyByScalar(nonUniformScale, scale, nonUniformScale);
var scaledBoundingSphere = this._scaledBoundingSphere;
scaledBoundingSphere.center = Cartesian3.multiplyComponents(this._boundingSphere.center, nonUniformScale, scaledBoundingSphere.center);
scaledBoundingSphere.radius = Cartesian3.maximumComponent(nonUniformScale) * this._initialRadius;
if (defined(this._rtcCenter)) {
Cartesian3.add(this._rtcCenter, scaledBoundingSphere.center, scaledBoundingSphere.center);
}
return scaledBoundingSphere;
}
},
/**
* When true
, this model is ready to render, i.e., the external binary, image,
* and shader files were downloaded and the WebGL resources were created. This is set to
* true
right before {@link Model#readyPromise} is resolved.
*
* @memberof Model.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets the promise that will be resolved when this model is ready to render, i.e., when the external binary, image,
* and shader files were downloaded and the WebGL resources were created.
*
* This promise is resolved at the end of the frame before the first frame the model is rendered in.
*
*
* @memberof Model.prototype
* @type {Promise.}
* @readonly
*
* @example
* // Play all animations at half-speed when the model is ready to render
* Cesium.when(model.readyPromise).then(function(model) {
* model.activeAnimations.addAll({
* speedup : 0.5
* });
* }).otherwise(function(error){
* window.alert(error);
* });
*
* @see Model#ready
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Determines if model WebGL resource creation will be spread out over several frames or
* block until completion once all glTF files are loaded.
*
* @memberof Model.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
asynchronous : {
get : function() {
return this._asynchronous;
}
},
/**
* When true
, each glTF mesh and primitive is pickable with {@link Scene#pick}. When false
, GPU memory is saved.
*
* @memberof Model.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
allowPicking : {
get : function() {
return this._allowPicking;
}
},
/**
* Determine if textures may continue to stream in after the model is loaded.
*
* @memberof Model.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
incrementallyLoadTextures : {
get : function() {
return this._incrementallyLoadTextures;
}
},
/**
* Return the number of pending texture loads.
*
* @memberof Model.prototype
*
* @type {Number}
* @readonly
*/
pendingTextureLoads : {
get : function() {
return defined(this._loadResources) ? this._loadResources.pendingTextureLoads : 0;
}
},
/**
* Returns true if the model was transformed this frame
*
* @memberof Model.prototype
*
* @type {Boolean}
* @readonly
*
* @private
*/
dirty : {
get : function() {
return this._dirty;
}
},
/**
* Gets or sets the condition specifying at what distance from the camera that this model will be displayed.
* @memberof Model.prototype
* @type {DistanceDisplayCondition}
* @default undefined
*/
distanceDisplayCondition : {
get : function() {
return this._distanceDisplayCondition;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far must be greater than near');
}
this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
}
}
});
var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
function silhouetteSupported(context) {
return context.stencilBuffer;
}
/**
* Determines if silhouettes are supported.
*
* @param {Scene} scene The scene.
* @returns {Boolean} true
if silhouettes are supported; otherwise, returns false
*/
Model.silhouetteSupported = function(scene) {
return silhouetteSupported(scene.context);
};
/**
* This function differs from the normal subarray function
* because it takes offset and length, rather than begin and end.
*/
function getSubarray(array, offset, length) {
return array.subarray(offset, offset + length);
}
function containsGltfMagic(uint8Array) {
var magic = getMagic(uint8Array);
return magic === 'glTF';
}
function parseBinaryGltfHeader(uint8Array) {
if (!containsGltfMagic(uint8Array)) {
throw new DeveloperError('bgltf is not a valid Binary glTF file.');
}
var view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
var byteOffset = 0;
byteOffset += sizeOfUint32; // Skip magic number
var version = view.getUint32(byteOffset, true);
if (version !== 1) {
throw new DeveloperError('Only Binary glTF version 1 is supported. Version ' + version + ' is not.');
}
byteOffset += sizeOfUint32;
byteOffset += sizeOfUint32; // Skip length
var sceneLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32 + sizeOfUint32; // Skip sceneFormat
var sceneOffset = byteOffset;
var binOffset = sceneOffset + sceneLength;
var json = getStringFromTypedArray(uint8Array, sceneOffset, sceneLength);
return {
glTF: JSON.parse(json),
binaryOffset: binOffset
};
}
/**
*
* Creates a model from a glTF asset. When the model is ready to render, i.e., when the external binary, image,
* and shader files are downloaded and the WebGL resources are created, the {@link Model#readyPromise} is resolved.
*
*
* The model can be a traditional glTF asset with a .gltf extension or a Binary glTF using the
* KHR_binary_glTF extension with a .glb extension.
*
*
* For high-precision rendering, Cesium supports the CESIUM_RTC extension, which introduces the
* CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated
* relative to a local origin.
*
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The url to the .gltf file.
* @param {Object} [options.headers] HTTP headers to send with the request.
* @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
* @param {Number} [options.scale=1.0] A uniform scale applied to this model.
* @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
* @param {Number} [options.maximumScale] The maximum scale for the model.
* @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
* @param {Boolean} [options.allowPicking=true] When true
, each glTF mesh and primitive is pickable with {@link Scene#pick}.
* @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
* @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
* @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from each light source.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each {@link DrawCommand} in the model.
* @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
*
* @returns {Model} The newly created model.
*
* @exception {DeveloperError} bgltf is not a valid Binary glTF file.
* @exception {DeveloperError} Only glTF Binary version 1 is supported.
*
* @example
* // Example 1. Create a model from a glTF asset
* var model = scene.primitives.add(Cesium.Model.fromGltf({
* url : './duck/duck.gltf'
* }));
*
* @example
* // Example 2. Create model and provide all properties and events
* var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*
* var model = scene.primitives.add(Cesium.Model.fromGltf({
* url : './duck/duck.gltf',
* show : true, // default
* modelMatrix : modelMatrix,
* scale : 2.0, // double size
* minimumPixelSize : 128, // never smaller than 128 pixels
* maximumScale: 20000, // never larger than 20000 * model size (overrides minimumPixelSize)
* allowPicking : false, // not pickable
* debugShowBoundingVolume : false, // default
* debugWireframe : false
* }));
*
* model.readyPromise.then(function(model) {
* // Play all animations when the model is ready to render
* model.activeAnimations.addAll();
* });
*/
Model.fromGltf = function(options) {
if (!defined(options) || !defined(options.url)) {
throw new DeveloperError('options.url is required');
}
var url = options.url;
// If no cache key is provided, use the absolute URL, since two URLs with
// different relative paths could point to the same model.
var cacheKey = defaultValue(options.cacheKey, getAbsoluteUri(url));
options = clone(options);
options.basePath = getBaseUri(url);
options.cacheKey = cacheKey;
var model = new Model(options);
options.headers = defined(options.headers) ? clone(options.headers) : {};
if (!defined(options.headers.Accept)) {
options.headers.Accept = defaultModelAccept;
}
var cachedGltf = gltfCache[cacheKey];
if (!defined(cachedGltf)) {
cachedGltf = new CachedGltf({
ready : false
});
cachedGltf.count = 1;
cachedGltf.modelsToLoad.push(model);
setCachedGltf(model, cachedGltf);
gltfCache[cacheKey] = cachedGltf;
loadArrayBuffer(url, options.headers).then(function(arrayBuffer) {
var array = new Uint8Array(arrayBuffer);
if (containsGltfMagic(array)) {
// Load binary glTF
var result = parseBinaryGltfHeader(array);
// KHR_binary_glTF is from the beginning of the binary section
if (result.binaryOffset !== 0) {
array = array.subarray(result.binaryOffset);
}
cachedGltf.makeReady(result.glTF, array);
} else {
// Load text (JSON) glTF
var json = getStringFromTypedArray(array);
cachedGltf.makeReady(JSON.parse(json));
}
}).otherwise(getFailedLoadFunction(model, 'model', url));
} else if (!cachedGltf.ready) {
// Cache hit but the loadArrayBuffer() or loadText() request is still pending
++cachedGltf.count;
cachedGltf.modelsToLoad.push(model);
}
// else if the cached glTF is defined and ready, the
// model constructor will pick it up using the cache key.
return model;
};
/**
* For the unit tests to verify model caching.
*
* @private
*/
Model._gltfCache = gltfCache;
function getRuntime(model, runtimeName, name) {
if (model._state !== ModelState.LOADED) {
throw new DeveloperError('The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true.');
}
if (!defined(name)) {
throw new DeveloperError('name is required.');
}
return (model._runtime[runtimeName])[name];
}
/**
* Returns the glTF node with the given name
property. This is used to
* modify a node's transform for animation outside of glTF animations.
*
* @param {String} name The glTF name of the node.
* @returns {ModelNode} The node or undefined
if no node with name
exists.
*
* @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true.
*
* @example
* // Apply non-uniform scale to node LOD3sp
* var node = model.getNode('LOD3sp');
* node.matrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(5.0, 1.0, 1.0), node.matrix);
*/
Model.prototype.getNode = function(name) {
var node = getRuntime(this, 'nodesByName', name);
return defined(node) ? node.publicNode : undefined;
};
/**
* Returns the glTF mesh with the given name
property.
*
* @param {String} name The glTF name of the mesh.
*
* @returns {ModelMesh} The mesh or undefined
if no mesh with name
exists.
*
* @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true.
*/
Model.prototype.getMesh = function(name) {
return getRuntime(this, 'meshesByName', name);
};
/**
* Returns the glTF material with the given name
property.
*
* @param {String} name The glTF name of the material.
* @returns {ModelMaterial} The material or undefined
if no material with name
exists.
*
* @exception {DeveloperError} The model is not loaded. Use Model.readyPromise or wait for Model.ready to be true.
*/
Model.prototype.getMaterial = function(name) {
return getRuntime(this, 'materialsByName', name);
};
var aMinScratch = new Cartesian3();
var aMaxScratch = new Cartesian3();
function getAccessorMinMax(gltf, accessorId) {
var accessor = gltf.accessors[accessorId];
var extensions = accessor.extensions;
var accessorMin = accessor.min;
var accessorMax = accessor.max;
// If this accessor is quantized, we should use the decoded min and max
if (defined(extensions)) {
var quantizedAttributes = extensions.WEB3D_quantized_attributes;
if (defined(quantizedAttributes)) {
accessorMin = quantizedAttributes.decodedMin;
accessorMax = quantizedAttributes.decodedMax;
}
}
return {
min : accessorMin,
max : accessorMax
};
}
function computeBoundingSphere(gltf) {
var gltfNodes = gltf.nodes;
var gltfMeshes = gltf.meshes;
var rootNodes = gltf.scenes[gltf.scene].nodes;
var rootNodesLength = rootNodes.length;
var nodeStack = [];
var min = new Cartesian3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
var max = new Cartesian3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
for (var i = 0; i < rootNodesLength; ++i) {
var n = gltfNodes[rootNodes[i]];
n._transformToRoot = getTransform(n);
nodeStack.push(n);
while (nodeStack.length > 0) {
n = nodeStack.pop();
var transformToRoot = n._transformToRoot;
var meshes = n.meshes;
if (defined(meshes)) {
var meshesLength = meshes.length;
for (var j = 0; j < meshesLength; ++j) {
var primitives = gltfMeshes[meshes[j]].primitives;
var primitivesLength = primitives.length;
for (var m = 0; m < primitivesLength; ++m) {
var positionAccessor = primitives[m].attributes.POSITION;
if (defined(positionAccessor)) {
var minMax = getAccessorMinMax(gltf, positionAccessor);
var aMin = Cartesian3.fromArray(minMax.min, 0, aMinScratch);
var aMax = Cartesian3.fromArray(minMax.max, 0, aMaxScratch);
if (defined(min) && defined(max)) {
Matrix4.multiplyByPoint(transformToRoot, aMin, aMin);
Matrix4.multiplyByPoint(transformToRoot, aMax, aMax);
Cartesian3.minimumByComponent(min, aMin, min);
Cartesian3.maximumByComponent(max, aMax, max);
}
}
}
}
}
var children = n.children;
var childrenLength = children.length;
for (var k = 0; k < childrenLength; ++k) {
var child = gltfNodes[children[k]];
child._transformToRoot = getTransform(child);
Matrix4.multiplyTransformation(transformToRoot, child._transformToRoot, child._transformToRoot);
nodeStack.push(child);
}
delete n._transformToRoot;
}
}
var boundingSphere = BoundingSphere.fromCornerPoints(min, max);
return BoundingSphere.transformWithoutScale(boundingSphere, yUpToZUp, boundingSphere);
}
///////////////////////////////////////////////////////////////////////////
function getFailedLoadFunction(model, type, path) {
return function() {
model._state = ModelState.FAILED;
model._readyPromise.reject(new RuntimeError('Failed to load ' + type + ': ' + path));
};
}
function bufferLoad(model, id) {
return function(arrayBuffer) {
var loadResources = model._loadResources;
loadResources.buffers[id] = new Uint8Array(arrayBuffer);
--loadResources.pendingBufferLoads;
};
}
function parseBuffers(model) {
var buffers = model.gltf.buffers;
for (var id in buffers) {
if (buffers.hasOwnProperty(id)) {
var buffer = buffers[id];
// The extension 'KHR_binary_glTF' uses a special buffer entitled just 'binary_glTF'.
// The 'KHR_binary_glTF' check is for backwards compatibility for the Cesium model converter
// circa Cesium 1.15-1.20 when the converter incorrectly used the buffer name 'KHR_binary_glTF'.
if ((id === 'binary_glTF') || (id === 'KHR_binary_glTF')) {
// Buffer is the binary glTF file itself that is already loaded
var loadResources = model._loadResources;
loadResources.buffers[id] = model._cachedGltf.bgltf;
}
else if (buffer.type === 'arraybuffer') {
++model._loadResources.pendingBufferLoads;
var uri = new Uri(buffer.uri);
var bufferPath = uri.resolve(model._baseUri).toString();
loadArrayBuffer(bufferPath).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath));
}
}
}
}
function parseBufferViews(model) {
var bufferViews = model.gltf.bufferViews;
for (var id in bufferViews) {
if (bufferViews.hasOwnProperty(id)) {
if (bufferViews[id].target === WebGLConstants.ARRAY_BUFFER) {
model._loadResources.buffersToCreate.enqueue(id);
}
}
}
}
function shaderLoad(model, id) {
return function(source) {
var loadResources = model._loadResources;
loadResources.shaders[id] = {
source : source,
bufferView : undefined
};
--loadResources.pendingShaderLoads;
};
}
function parseShaders(model) {
var shaders = model.gltf.shaders;
for (var id in shaders) {
if (shaders.hasOwnProperty(id)) {
var shader = shaders[id];
// Shader references either uri (external or base64-encoded) or bufferView
if (defined(shader.extras) && defined(shader.extras.source)) {
model._loadResources.shaders[id] = {
source : shader.extras.source,
bufferView : undefined
};
}
else if (defined(shader.extensions) && defined(shader.extensions.KHR_binary_glTF)) {
var binary = shader.extensions.KHR_binary_glTF;
model._loadResources.shaders[id] = {
source : undefined,
bufferView : binary.bufferView
};
} else {
++model._loadResources.pendingShaderLoads;
var uri = new Uri(shader.uri);
var shaderPath = uri.resolve(model._baseUri).toString();
loadText(shaderPath).then(shaderLoad(model, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath));
}
}
}
}
function parsePrograms(model) {
var programs = model.gltf.programs;
for (var id in programs) {
if (programs.hasOwnProperty(id)) {
model._loadResources.programsToCreate.enqueue(id);
}
}
}
function imageLoad(model, id) {
return function(image) {
var loadResources = model._loadResources;
--loadResources.pendingTextureLoads;
loadResources.texturesToCreate.enqueue({
id : id,
image : image,
bufferView : undefined
});
};
}
function parseTextures(model) {
var images = model.gltf.images;
var textures = model.gltf.textures;
for (var id in textures) {
if (textures.hasOwnProperty(id)) {
var gltfImage = images[textures[id].source];
// Image references either uri (external or base64-encoded) or bufferView
if (defined(gltfImage.extensions) && defined(gltfImage.extensions.KHR_binary_glTF)) {
var binary = gltfImage.extensions.KHR_binary_glTF;
model._loadResources.texturesToCreateFromBufferView.enqueue({
id : id,
image : undefined,
bufferView : binary.bufferView,
mimeType : binary.mimeType
});
} else {
++model._loadResources.pendingTextureLoads;
var uri = new Uri(gltfImage.uri);
var imagePath = uri.resolve(model._baseUri).toString();
loadImage(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
}
}
}
}
var nodeTranslationScratch = new Cartesian3();
var nodeQuaternionScratch = new Quaternion();
var nodeScaleScratch = new Cartesian3();
function getTransform(node) {
if (defined(node.matrix)) {
return Matrix4.fromArray(node.matrix);
}
return Matrix4.fromTranslationQuaternionRotationScale(
Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch),
Quaternion.unpack(node.rotation, 0, nodeQuaternionScratch),
Cartesian3.fromArray(node.scale, 0 , nodeScaleScratch));
}
function parseNodes(model) {
var runtimeNodes = {};
var runtimeNodesByName = {};
var skinnedNodes = [];
var skinnedNodesIds = model._loadResources.skinnedNodesIds;
var nodes = model.gltf.nodes;
for (var id in nodes) {
if (nodes.hasOwnProperty(id)) {
var node = nodes[id];
var runtimeNode = {
// Animation targets
matrix : undefined,
translation : undefined,
rotation : undefined,
scale : undefined,
// Per-node show inherited from parent
computedShow : true,
// Computed transforms
transformToRoot : new Matrix4(),
computedMatrix : new Matrix4(),
dirtyNumber : 0, // The frame this node was made dirty by an animation; for graph traversal
// Rendering
commands : [], // empty for transform, light, and camera nodes
// Skinned node
inverseBindMatrices : undefined, // undefined when node is not skinned
bindShapeMatrix : undefined, // undefined when node is not skinned or identity
joints : [], // empty when node is not skinned
computedJointMatrices : [], // empty when node is not skinned
// Joint node
jointName : node.jointName, // undefined when node is not a joint
// Graph pointers
children : [], // empty for leaf nodes
parents : [], // empty for root nodes
// Publicly-accessible ModelNode instance to modify animation targets
publicNode : undefined
};
runtimeNode.publicNode = new ModelNode(model, node, runtimeNode, id, getTransform(node));
runtimeNodes[id] = runtimeNode;
runtimeNodesByName[node.name] = runtimeNode;
if (defined(node.skin)) {
skinnedNodesIds.push(id);
skinnedNodes.push(runtimeNode);
}
}
}
model._runtime.nodes = runtimeNodes;
model._runtime.nodesByName = runtimeNodesByName;
model._runtime.skinnedNodes = skinnedNodes;
}
function parseMaterials(model) {
var runtimeMaterialsByName = {};
var runtimeMaterialsById = {};
var materials = model.gltf.materials;
var uniformMaps = model._uniformMaps;
for (var id in materials) {
if (materials.hasOwnProperty(id)) {
// Allocated now so ModelMaterial can keep a reference to it.
uniformMaps[id] = {
uniformMap : undefined,
values : undefined,
jointMatrixUniformName : undefined
};
var material = materials[id];
var modelMaterial = new ModelMaterial(model, material, id);
runtimeMaterialsByName[material.name] = modelMaterial;
runtimeMaterialsById[id] = modelMaterial;
}
}
model._runtime.materialsByName = runtimeMaterialsByName;
model._runtime.materialsById = runtimeMaterialsById;
}
function parseMeshes(model) {
var runtimeMeshesByName = {};
var runtimeMaterialsById = model._runtime.materialsById;
var meshes = model.gltf.meshes;
var usesQuantizedAttributes = usesExtension(model, 'WEB3D_quantized_attributes');
for (var id in meshes) {
if (meshes.hasOwnProperty(id)) {
var mesh = meshes[id];
runtimeMeshesByName[mesh.name] = new ModelMesh(mesh, runtimeMaterialsById, id);
if (usesQuantizedAttributes) {
// Cache primitives according to their program
var primitives = mesh.primitives;
var primitivesLength = primitives.length;
for (var i = 0; i < primitivesLength; i++) {
var primitive = primitives[i];
var programId = getProgramForPrimitive(model, primitive);
var programPrimitives = model._programPrimitives[programId];
if (!defined(programPrimitives)) {
programPrimitives = [];
model._programPrimitives[programId] = programPrimitives;
}
programPrimitives.push(primitive);
}
}
}
}
model._runtime.meshesByName = runtimeMeshesByName;
}
function parse(model) {
if (!model._loadRendererResourcesFromCache) {
parseBuffers(model);
parseBufferViews(model);
parseShaders(model);
parsePrograms(model);
parseTextures(model);
}
parseMaterials(model);
parseMeshes(model);
parseNodes(model);
}
function usesExtension(model, extension) {
var cachedExtensionsUsed = model._extensionsUsed;
if (!defined(cachedExtensionsUsed)) {
var extensionsUsed = model.gltf.extensionsUsed;
cachedExtensionsUsed = {};
var extensionsLength = extensionsUsed.length;
for (var i = 0; i < extensionsLength; i++) {
cachedExtensionsUsed[extensionsUsed[i]] = true;
}
}
return defined(cachedExtensionsUsed[extension]);
}
///////////////////////////////////////////////////////////////////////////
function createBuffers(model, context) {
var loadResources = model._loadResources;
if (loadResources.pendingBufferLoads !== 0) {
return;
}
var bufferView;
var bufferViews = model.gltf.bufferViews;
var rendererBuffers = model._rendererResources.buffers;
while (loadResources.buffersToCreate.length > 0) {
var bufferViewId = loadResources.buffersToCreate.dequeue();
bufferView = bufferViews[bufferViewId];
// Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below.
var vertexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : loadResources.getBuffer(bufferView),
usage : BufferUsage.STATIC_DRAW
});
vertexBuffer.vertexArrayDestroyable = false;
rendererBuffers[bufferViewId] = vertexBuffer;
}
// The Cesium Renderer requires knowing the datatype for an index buffer
// at creation type, which is not part of the glTF bufferview so loop
// through glTF accessors to create the bufferview's index buffer.
var accessors = model.gltf.accessors;
for (var id in accessors) {
if (accessors.hasOwnProperty(id)) {
var accessor = accessors[id];
bufferView = bufferViews[accessor.bufferView];
if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(rendererBuffers[accessor.bufferView])) {
var indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : loadResources.getBuffer(bufferView),
usage : BufferUsage.STATIC_DRAW,
indexDatatype : accessor.componentType
});
indexBuffer.vertexArrayDestroyable = false;
rendererBuffers[accessor.bufferView] = indexBuffer;
// In theory, several glTF accessors with different componentTypes could
// point to the same glTF bufferView, which would break this.
// In practice, it is unlikely as it will be UNSIGNED_SHORT.
}
}
}
}
function createAttributeLocations(model, attributes) {
var attributeLocations = {};
var length = attributes.length;
var i;
// Set the position attribute to the 0th index. In some WebGL implementations the shader
// will not work correctly if the 0th attribute is not active. For example, some glTF models
// list the normal attribute first but derived shaders like the cast-shadows shader do not use
// the normal attribute.
for (i = 1; i < length; ++i) {
var attribute = attributes[i];
if (/position/i.test(attribute)) {
attributes[i] = attributes[0];
attributes[0] = attribute;
break;
}
}
for (i = 0; i < length; ++i) {
attributeLocations[attributes[i]] = i;
}
return attributeLocations;
}
function getShaderSource(model, shader) {
if (defined(shader.source)) {
return shader.source;
}
var loadResources = model._loadResources;
var gltf = model.gltf;
var bufferView = gltf.bufferViews[shader.bufferView];
return getStringFromTypedArray(loadResources.getBuffer(bufferView));
}
function replaceAllButFirstInString(string, find, replace) {
var index = string.indexOf(find);
return string.replace(new RegExp(find, 'g'), function(match, offset, all) {
return index === offset ? match : replace;
});
}
function getProgramForPrimitive(model, primitive) {
var gltf = model.gltf;
var materialId = primitive.material;
var material = gltf.materials[materialId];
var techniqueId = material.technique;
var technique = gltf.techniques[techniqueId];
return technique.program;
}
function getQuantizedAttributes(model, accessorId) {
var gltf = model.gltf;
var accessor = gltf.accessors[accessorId];
var extensions = accessor.extensions;
if (defined(extensions)) {
return extensions.WEB3D_quantized_attributes;
}
return undefined;
}
function getAttributeVariableName(model, primitive, attributeSemantic) {
var gltf = model.gltf;
var materialId = primitive.material;
var material = gltf.materials[materialId];
var techniqueId = material.technique;
var technique = gltf.techniques[techniqueId];
for (var parameter in technique.parameters) {
if (technique.parameters.hasOwnProperty(parameter)) {
var semantic = technique.parameters[parameter].semantic;
if (semantic === attributeSemantic) {
var attributes = technique.attributes;
for (var attributeVarName in attributes) {
if (attributes.hasOwnProperty(attributeVarName)) {
var name = attributes[attributeVarName];
if (name === parameter) {
return attributeVarName;
}
}
}
}
}
}
return undefined;
}
function modifyShaderForQuantizedAttributes(shader, programName, model, context) {
var quantizedUniforms = {};
model._quantizedUniforms[programName] = quantizedUniforms;
var primitives = model._programPrimitives[programName];
for (var i = 0; i < primitives.length; i++) {
var primitive = primitives[i];
if (getProgramForPrimitive(model, primitive) === programName) {
for (var attributeSemantic in primitive.attributes) {
if (primitive.attributes.hasOwnProperty(attributeSemantic)) {
var decodeUniformVarName = 'gltf_u_dec_' + attributeSemantic.toLowerCase();
var decodeUniformVarNameScale = decodeUniformVarName + '_scale';
var decodeUniformVarNameTranslate = decodeUniformVarName + '_translate';
if (!defined(quantizedUniforms[decodeUniformVarName]) && !defined(quantizedUniforms[decodeUniformVarNameScale])) {
var accessorId = primitive.attributes[attributeSemantic];
var quantizedAttributes = getQuantizedAttributes(model, accessorId);
if (defined(quantizedAttributes)) {
var attributeVarName = getAttributeVariableName(model, primitive, attributeSemantic);
var decodeMatrix = quantizedAttributes.decodeMatrix;
var newMain = 'gltf_decoded_' + attributeSemantic;
var decodedAttributeVarName = attributeVarName.replace('a_', 'gltf_a_dec_');
var size = Math.floor(Math.sqrt(decodeMatrix.length));
// replace usages of the original attribute with the decoded version, but not the declaration
shader = replaceAllButFirstInString(shader, attributeVarName, decodedAttributeVarName);
// declare decoded attribute
var variableType;
if (size > 2) {
variableType = 'vec' + (size - 1);
} else {
variableType = 'float';
}
shader = variableType + ' ' + decodedAttributeVarName + ';\n' + shader;
// splice decode function into the shader - attributes are pre-multiplied with the decode matrix
// uniform in the shader (32-bit floating point)
var decode = '';
if (size === 5) {
// separate scale and translate since glsl doesn't have mat5
shader = 'uniform mat4 ' + decodeUniformVarNameScale + ';\n' + shader;
shader = 'uniform vec4 ' + decodeUniformVarNameTranslate + ';\n' + shader;
decode = '\n' +
'void main() {\n' +
' ' + decodedAttributeVarName + ' = ' + decodeUniformVarNameScale + ' * ' + attributeVarName + ' + ' + decodeUniformVarNameTranslate + ';\n' +
' ' + newMain + '();\n' +
'}\n';
quantizedUniforms[decodeUniformVarNameScale] = {mat : 4};
quantizedUniforms[decodeUniformVarNameTranslate] = {vec : 4};
}
else {
shader = 'uniform mat' + size + ' ' + decodeUniformVarName + ';\n' + shader;
decode = '\n' +
'void main() {\n' +
' ' + decodedAttributeVarName + ' = ' + variableType + '(' + decodeUniformVarName + ' * vec' + size + '(' + attributeVarName + ',1.0));\n' +
' ' + newMain + '();\n' +
'}\n';
quantizedUniforms[decodeUniformVarName] = {mat : size};
}
shader = ShaderSource.replaceMain(shader, newMain);
shader += decode;
}
}
}
}
}
}
// This is not needed after the program is processed, free the memory
model._programPrimitives[programName] = undefined;
return shader;
}
function hasPremultipliedAlpha(model) {
var gltf = model.gltf;
return defined(gltf.asset) ? defaultValue(gltf.asset.premultipliedAlpha, false) : false;
}
function modifyShaderForColor(shader, premultipliedAlpha) {
shader = ShaderSource.replaceMain(shader, 'gltf_blend_main');
shader +=
'uniform vec4 gltf_color; \n' +
'uniform float gltf_colorBlend; \n' +
'void main() \n' +
'{ \n' +
' gltf_blend_main(); \n';
// Un-premultiply the alpha so that blending is correct.
// Avoid divide-by-zero. The code below is equivalent to:
// if (gl_FragColor.a > 0.0)
// {
// gl_FragColor.rgb /= gl_FragColor.a;
// }
if (premultipliedAlpha) {
shader +=
' float alpha = 1.0 - ceil(gl_FragColor.a) + gl_FragColor.a; \n' +
' gl_FragColor.rgb /= alpha; \n';
}
shader +=
' gl_FragColor.rgb = mix(gl_FragColor.rgb, gltf_color.rgb, gltf_colorBlend); \n' +
' float highlight = ceil(gltf_colorBlend); \n' +
' gl_FragColor.rgb *= mix(gltf_color.rgb, vec3(1.0), highlight); \n' +
' gl_FragColor.a *= gltf_color.a; \n' +
'} \n';
return shader;
}
function modifyShader(shader, programName, callback) {
if (defined(callback)) {
shader = callback(shader, programName);
}
return shader;
}
function createProgram(id, model, context) {
var programs = model.gltf.programs;
var shaders = model._loadResources.shaders;
var program = programs[id];
var attributeLocations = createAttributeLocations(model, program.attributes);
var vs = getShaderSource(model, shaders[program.vertexShader]);
var fs = getShaderSource(model, shaders[program.fragmentShader]);
// Add pre-created attributes to attributeLocations
var attributesLength = program.attributes.length;
var precreatedAttributes = model._precreatedAttributes;
if (defined(precreatedAttributes)) {
for (var attrName in precreatedAttributes) {
if (precreatedAttributes.hasOwnProperty(attrName)) {
attributeLocations[attrName] = attributesLength++;
}
}
}
if (usesExtension(model, 'WEB3D_quantized_attributes')) {
vs = modifyShaderForQuantizedAttributes(vs, id, model, context);
}
var premultipliedAlpha = hasPremultipliedAlpha(model);
var blendFS = modifyShaderForColor(fs, premultipliedAlpha);
var drawVS = modifyShader(vs, id, model._vertexShaderLoaded);
var drawFS = modifyShader(blendFS, id, model._fragmentShaderLoaded);
model._rendererResources.programs[id] = ShaderProgram.fromCache({
context : context,
vertexShaderSource : drawVS,
fragmentShaderSource : drawFS,
attributeLocations : attributeLocations
});
if (model.allowPicking) {
// PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181
var pickVS = modifyShader(vs, id, model._pickVertexShaderLoaded);
var pickFS = modifyShader(fs, id, model._pickFragmentShaderLoaded);
if (!model._pickFragmentShaderLoaded) {
pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform');
}
model._rendererResources.pickPrograms[id] = ShaderProgram.fromCache({
context : context,
vertexShaderSource : pickVS,
fragmentShaderSource : pickFS,
attributeLocations : attributeLocations
});
}
}
function createPrograms(model, context) {
var loadResources = model._loadResources;
var id;
if (loadResources.pendingShaderLoads !== 0) {
return;
}
// PERFORMANCE_IDEA: this could be more fine-grained by looking
// at the shader's bufferView's to determine the buffer dependencies.
if (loadResources.pendingBufferLoads !== 0) {
return;
}
if (model.asynchronous) {
// Create one program per frame
if (loadResources.programsToCreate.length > 0) {
id = loadResources.programsToCreate.dequeue();
createProgram(id, model, context);
}
} else {
// Create all loaded programs this frame
while (loadResources.programsToCreate.length > 0) {
id = loadResources.programsToCreate.dequeue();
createProgram(id, model, context);
}
}
}
function getOnImageCreatedFromTypedArray(loadResources, gltfTexture) {
return function(image) {
loadResources.texturesToCreate.enqueue({
id : gltfTexture.id,
image : image,
bufferView : undefined
});
--loadResources.pendingBufferViewToImage;
};
}
function loadTexturesFromBufferViews(model) {
var loadResources = model._loadResources;
if (loadResources.pendingBufferLoads !== 0) {
return;
}
while (loadResources.texturesToCreateFromBufferView.length > 0) {
var gltfTexture = loadResources.texturesToCreateFromBufferView.dequeue();
var gltf = model.gltf;
var bufferView = gltf.bufferViews[gltfTexture.bufferView];
var onload = getOnImageCreatedFromTypedArray(loadResources, gltfTexture);
var onerror = getFailedLoadFunction(model, 'image', 'id: ' + gltfTexture.id + ', bufferView: ' + gltfTexture.bufferView);
loadImageFromTypedArray(loadResources.getBuffer(bufferView), gltfTexture.mimeType).
then(onload).otherwise(onerror);
++loadResources.pendingBufferViewToImage;
}
}
function createSamplers(model, context) {
var loadResources = model._loadResources;
if (loadResources.createSamplers) {
loadResources.createSamplers = false;
var rendererSamplers = model._rendererResources.samplers;
var samplers = model.gltf.samplers;
for (var id in samplers) {
if (samplers.hasOwnProperty(id)) {
var sampler = samplers[id];
rendererSamplers[id] = new Sampler({
wrapS : sampler.wrapS,
wrapT : sampler.wrapT,
minificationFilter : sampler.minFilter,
magnificationFilter : sampler.magFilter
});
}
}
}
}
function createTexture(gltfTexture, model, context) {
var textures = model.gltf.textures;
var texture = textures[gltfTexture.id];
var rendererSamplers = model._rendererResources.samplers;
var sampler = rendererSamplers[texture.sampler];
var mipmap =
(sampler.minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
(sampler.minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
(sampler.minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
(sampler.minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
var requiresNpot = mipmap ||
(sampler.wrapS === TextureWrap.REPEAT) ||
(sampler.wrapS === TextureWrap.MIRRORED_REPEAT) ||
(sampler.wrapT === TextureWrap.REPEAT) ||
(sampler.wrapT === TextureWrap.MIRRORED_REPEAT);
var source = gltfTexture.image;
var npot = !CesiumMath.isPowerOfTwo(source.width) || !CesiumMath.isPowerOfTwo(source.height);
if (requiresNpot && npot) {
// WebGL requires power-of-two texture dimensions for mipmapping and REPEAT/MIRRORED_REPEAT wrap modes.
var canvas = document.createElement('canvas');
canvas.width = CesiumMath.nextPowerOfTwo(source.width);
canvas.height = CesiumMath.nextPowerOfTwo(source.height);
var canvasContext = canvas.getContext('2d');
canvasContext.drawImage(source, 0, 0, source.width, source.height, 0, 0, canvas.width, canvas.height);
source = canvas;
}
var tx;
if (texture.target === WebGLConstants.TEXTURE_2D) {
tx = new Texture({
context : context,
source : source,
pixelFormat : texture.internalFormat,
pixelDatatype : texture.type,
sampler : sampler,
flipY : false
});
}
// GLTF_SPEC: Support TEXTURE_CUBE_MAP. https://github.com/KhronosGroup/glTF/issues/40
if (mipmap) {
tx.generateMipmap();
}
model._rendererResources.textures[gltfTexture.id] = tx;
}
function createTextures(model, context) {
var loadResources = model._loadResources;
var gltfTexture;
if (model.asynchronous) {
// Create one texture per frame
if (loadResources.texturesToCreate.length > 0) {
gltfTexture = loadResources.texturesToCreate.dequeue();
createTexture(gltfTexture, model, context);
}
} else {
// Create all loaded textures this frame
while (loadResources.texturesToCreate.length > 0) {
gltfTexture = loadResources.texturesToCreate.dequeue();
createTexture(gltfTexture, model, context);
}
}
}
function getAttributeLocations(model, primitive) {
var gltf = model.gltf;
var techniques = gltf.techniques;
var materials = gltf.materials;
// Retrieve the compiled shader program to assign index values to attributes
var attributeLocations = {};
var technique = techniques[materials[primitive.material].technique];
var parameters = technique.parameters;
var attributes = technique.attributes;
var programAttributeLocations = model._rendererResources.programs[technique.program].vertexAttributes;
// Note: WebGL shader compiler may have optimized and removed some attributes from programAttributeLocations
for (var location in programAttributeLocations){
if (programAttributeLocations.hasOwnProperty(location)) {
var attribute = attributes[location];
var index = programAttributeLocations[location].index;
if (defined(attribute)) {
var parameter = parameters[attribute];
attributeLocations[parameter.semantic] = index;
} else {
// Pre-created attributes
attributeLocations[location] = index;
}
}
}
return attributeLocations;
}
function searchForest(forest, jointName, nodes) {
var length = forest.length;
for (var i = 0; i < length; ++i) {
var stack = [forest[i]]; // Push root node of tree
while (stack.length > 0) {
var id = stack.pop();
var n = nodes[id];
if (n.jointName === jointName) {
return id;
}
var children = n.children;
var childrenLength = children.length;
for (var k = 0; k < childrenLength; ++k) {
stack.push(children[k]);
}
}
}
// This should never happen; the skeleton should have a node for all joints in the skin.
return undefined;
}
function createJoints(model, runtimeSkins) {
var gltf = model.gltf;
var skins = gltf.skins;
var nodes = gltf.nodes;
var runtimeNodes = model._runtime.nodes;
var skinnedNodesIds = model._loadResources.skinnedNodesIds;
var length = skinnedNodesIds.length;
for (var j = 0; j < length; ++j) {
var id = skinnedNodesIds[j];
var skinnedNode = runtimeNodes[id];
var node = nodes[id];
var runtimeSkin = runtimeSkins[node.skin];
skinnedNode.inverseBindMatrices = runtimeSkin.inverseBindMatrices;
skinnedNode.bindShapeMatrix = runtimeSkin.bindShapeMatrix;
// 1. Find nodes with the names in node.skeletons (the node's skeletons)
// 2. These nodes form the root nodes of the forest to search for each joint in skin.jointNames. This search uses jointName, not the node's name.
// 3. Search for the joint name among the gltf node hierarchy instead of the runtime node hierarchy. Child links aren't set up yet for runtime nodes.
var forest = [];
var gltfSkeletons = node.skeletons;
var skeletonsLength = gltfSkeletons.length;
for (var k = 0; k < skeletonsLength; ++k) {
forest.push(gltfSkeletons[k]);
}
var gltfJointNames = skins[node.skin].jointNames;
var jointNamesLength = gltfJointNames.length;
for (var i = 0; i < jointNamesLength; ++i) {
var jointName = gltfJointNames[i];
var jointNode = runtimeNodes[searchForest(forest, jointName, nodes)];
skinnedNode.joints.push(jointNode);
}
}
}
function createSkins(model) {
var loadResources = model._loadResources;
if (loadResources.pendingBufferLoads !== 0) {
return;
}
if (!loadResources.createSkins) {
return;
}
loadResources.createSkins = false;
var gltf = model.gltf;
var accessors = gltf.accessors;
var skins = gltf.skins;
var runtimeSkins = {};
for (var id in skins) {
if (skins.hasOwnProperty(id)) {
var skin = skins[id];
var accessor = accessors[skin.inverseBindMatrices];
var bindShapeMatrix;
if (!Matrix4.equals(skin.bindShapeMatrix, Matrix4.IDENTITY)) {
bindShapeMatrix = Matrix4.clone(skin.bindShapeMatrix);
}
runtimeSkins[id] = {
inverseBindMatrices : ModelAnimationCache.getSkinInverseBindMatrices(model, accessor),
bindShapeMatrix : bindShapeMatrix // not used when undefined
};
}
}
createJoints(model, runtimeSkins);
}
function getChannelEvaluator(model, runtimeNode, targetPath, spline) {
return function(localAnimationTime) {
// Workaround for https://github.com/KhronosGroup/glTF/issues/219
//if (targetPath === 'translation') {
// return;
//}
runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]);
runtimeNode.dirtyNumber = model._maxDirtyNumber;
};
}
function createRuntimeAnimations(model) {
var loadResources = model._loadResources;
if (!loadResources.finishedPendingBufferLoads()) {
return;
}
if (!loadResources.createRuntimeAnimations) {
return;
}
loadResources.createRuntimeAnimations = false;
model._runtime.animations = {
};
var runtimeNodes = model._runtime.nodes;
var animations = model.gltf.animations;
var accessors = model.gltf.accessors;
for (var animationId in animations) {
if (animations.hasOwnProperty(animationId)) {
var animation = animations[animationId];
var channels = animation.channels;
var parameters = animation.parameters;
var samplers = animation.samplers;
var parameterValues = {};
for (var name in parameters) {
if (parameters.hasOwnProperty(name)) {
parameterValues[name] = ModelAnimationCache.getAnimationParameterValues(model, accessors[parameters[name]]);
}
}
// Find start and stop time for the entire animation
var startTime = Number.MAX_VALUE;
var stopTime = -Number.MAX_VALUE;
var length = channels.length;
var channelEvaluators = new Array(length);
for (var i = 0; i < length; ++i) {
var channel = channels[i];
var target = channel.target;
var sampler = samplers[channel.sampler];
var times = parameterValues[sampler.input];
startTime = Math.min(startTime, times[0]);
stopTime = Math.max(stopTime, times[times.length - 1]);
var spline = ModelAnimationCache.getAnimationSpline(model, animationId, animation, channel.sampler, sampler, parameterValues);
// GLTF_SPEC: Support more targets like materials. https://github.com/KhronosGroup/glTF/issues/142
channelEvaluators[i] = getChannelEvaluator(model, runtimeNodes[target.id], target.path, spline);
}
model._runtime.animations[animationId] = {
startTime : startTime,
stopTime : stopTime,
channelEvaluators : channelEvaluators
};
}
}
}
function createVertexArrays(model, context) {
var loadResources = model._loadResources;
if (!loadResources.finishedBuffersCreation() || !loadResources.finishedProgramCreation()) {
return;
}
if (!loadResources.createVertexArrays) {
return;
}
loadResources.createVertexArrays = false;
var rendererBuffers = model._rendererResources.buffers;
var rendererVertexArrays = model._rendererResources.vertexArrays;
var gltf = model.gltf;
var accessors = gltf.accessors;
var meshes = gltf.meshes;
for (var meshId in meshes) {
if (meshes.hasOwnProperty(meshId)) {
var primitives = meshes[meshId].primitives;
var primitivesLength = primitives.length;
for (var i = 0; i < primitivesLength; ++i) {
var primitive = primitives[i];
// GLTF_SPEC: This does not take into account attribute arrays,
// indicated by when an attribute points to a parameter with a
// count property.
//
// https://github.com/KhronosGroup/glTF/issues/258
var attributeLocations = getAttributeLocations(model, primitive);
var attributeName;
var attributeLocation;
var attribute;
var attributes = [];
var primitiveAttributes = primitive.attributes;
for (attributeName in primitiveAttributes) {
if (primitiveAttributes.hasOwnProperty(attributeName)) {
attributeLocation = attributeLocations[attributeName];
// Skip if the attribute is not used by the material, e.g., because the asset was exported
// with an attribute that wasn't used and the asset wasn't optimized.
if (defined(attributeLocation)) {
var a = accessors[primitiveAttributes[attributeName]];
attributes.push({
index : attributeLocation,
vertexBuffer : rendererBuffers[a.bufferView],
componentsPerAttribute : getBinaryAccessor(a).componentsPerAttribute,
componentDatatype : a.componentType,
normalize : false,
offsetInBytes : a.byteOffset,
strideInBytes : a.byteStride
});
}
}
}
// Add pre-created attributes
var precreatedAttributes = model._precreatedAttributes;
if (defined(precreatedAttributes)) {
for (attributeName in precreatedAttributes) {
if (precreatedAttributes.hasOwnProperty(attributeName)) {
attributeLocation = attributeLocations[attributeName];
if (defined(attributeLocation)) {
attribute = precreatedAttributes[attributeName];
attribute.index = attributeLocation;
attributes.push(attribute);
}
}
}
}
var indexBuffer;
if (defined(primitive.indices)) {
var accessor = accessors[primitive.indices];
indexBuffer = rendererBuffers[accessor.bufferView];
}
rendererVertexArrays[meshId + '.primitive.' + i] = new VertexArray({
context : context,
attributes : attributes,
indexBuffer : indexBuffer
});
}
}
}
}
function getBooleanStates(states) {
// GLTF_SPEC: SAMPLE_ALPHA_TO_COVERAGE not used by Cesium
var booleanStates = {};
booleanStates[WebGLConstants.BLEND] = false;
booleanStates[WebGLConstants.CULL_FACE] = false;
booleanStates[WebGLConstants.DEPTH_TEST] = false;
booleanStates[WebGLConstants.POLYGON_OFFSET_FILL] = false;
booleanStates[WebGLConstants.SCISSOR_TEST] = false;
var enable = states.enable;
var length = enable.length;
var i;
for (i = 0; i < length; ++i) {
booleanStates[enable[i]] = true;
}
return booleanStates;
}
function createRenderStates(model, context) {
var loadResources = model._loadResources;
var techniques = model.gltf.techniques;
if (loadResources.createRenderStates) {
loadResources.createRenderStates = false;
for (var id in techniques) {
if (techniques.hasOwnProperty(id)) {
createRenderStateForTechnique(model, id, context);
}
}
}
}
function createRenderStateForTechnique(model, id, context) {
var rendererRenderStates = model._rendererResources.renderStates;
var techniques = model.gltf.techniques;
var technique = techniques[id];
var states = technique.states;
var booleanStates = getBooleanStates(states);
var statesFunctions = defaultValue(states.functions, defaultValue.EMPTY_OBJECT);
var blendColor = defaultValue(statesFunctions.blendColor, [0.0, 0.0, 0.0, 0.0]);
var blendEquationSeparate = defaultValue(statesFunctions.blendEquationSeparate, [
WebGLConstants.FUNC_ADD,
WebGLConstants.FUNC_ADD]);
var blendFuncSeparate = defaultValue(statesFunctions.blendFuncSeparate, [
WebGLConstants.ONE,
WebGLConstants.ZERO,
WebGLConstants.ONE,
WebGLConstants.ZERO]);
var colorMask = defaultValue(statesFunctions.colorMask, [true, true, true, true]);
var depthRange = defaultValue(statesFunctions.depthRange, [0.0, 1.0]);
var polygonOffset = defaultValue(statesFunctions.polygonOffset, [0.0, 0.0]);
var scissor = defaultValue(statesFunctions.scissor, [0.0, 0.0, 0.0, 0.0]);
// Change the render state to use traditional alpha blending instead of premultiplied alpha blending
if (booleanStates[WebGLConstants.BLEND] && hasPremultipliedAlpha(model)) {
if ((blendFuncSeparate[0] === WebGLConstants.ONE) && (blendFuncSeparate[1] === WebGLConstants.ONE_MINUS_SRC_ALPHA)) {
blendFuncSeparate[0] = WebGLConstants.SRC_ALPHA;
blendFuncSeparate[1] = WebGLConstants.ONE_MINUS_SRC_ALPHA;
blendFuncSeparate[2] = WebGLConstants.SRC_ALPHA;
blendFuncSeparate[3] = WebGLConstants.ONE_MINUS_SRC_ALPHA;
}
}
rendererRenderStates[id] = RenderState.fromCache({
frontFace : defined(statesFunctions.frontFace) ? statesFunctions.frontFace[0] : WebGLConstants.CCW,
cull : {
enabled : booleanStates[WebGLConstants.CULL_FACE],
face : defined(statesFunctions.cullFace) ? statesFunctions.cullFace[0] : WebGLConstants.BACK
},
lineWidth : defined(statesFunctions.lineWidth) ? statesFunctions.lineWidth[0] : 1.0,
polygonOffset : {
enabled : booleanStates[WebGLConstants.POLYGON_OFFSET_FILL],
factor : polygonOffset[0],
units : polygonOffset[1]
},
scissorTest : {
enabled : booleanStates[WebGLConstants.SCISSOR_TEST],
rectangle : {
x : scissor[0],
y : scissor[1],
width : scissor[2],
height : scissor[3]
}
},
depthRange : {
near : depthRange[0],
far : depthRange[1]
},
depthTest : {
enabled : booleanStates[WebGLConstants.DEPTH_TEST],
func : defined(statesFunctions.depthFunc) ? statesFunctions.depthFunc[0] : WebGLConstants.LESS
},
colorMask : {
red : colorMask[0],
green : colorMask[1],
blue : colorMask[2],
alpha : colorMask[3]
},
depthMask : defined(statesFunctions.depthMask) ? statesFunctions.depthMask[0] : true,
blending : {
enabled : booleanStates[WebGLConstants.BLEND],
color : {
red : blendColor[0],
green : blendColor[1],
blue : blendColor[2],
alpha : blendColor[3]
},
equationRgb : blendEquationSeparate[0],
equationAlpha : blendEquationSeparate[1],
functionSourceRgb : blendFuncSeparate[0],
functionDestinationRgb : blendFuncSeparate[1],
functionSourceAlpha : blendFuncSeparate[2],
functionDestinationAlpha : blendFuncSeparate[3]
}
});
}
// This doesn't support LOCAL, which we could add if it is ever used.
var scratchTranslationRtc = new Cartesian3();
var gltfSemanticUniforms = {
MODEL : function(uniformState, model) {
return function() {
return uniformState.model;
};
},
VIEW : function(uniformState, model) {
return function() {
return uniformState.view;
};
},
PROJECTION : function(uniformState, model) {
return function() {
return uniformState.projection;
};
},
MODELVIEW : function(uniformState, model) {
return function() {
return uniformState.modelView;
};
},
CESIUM_RTC_MODELVIEW : function(uniformState, model) {
// CESIUM_RTC extension
var mvRtc = new Matrix4();
return function() {
Matrix4.getTranslation(uniformState.model, scratchTranslationRtc);
Cartesian3.add(scratchTranslationRtc, model._rtcCenter, scratchTranslationRtc);
Matrix4.multiplyByPoint(uniformState.view, scratchTranslationRtc, scratchTranslationRtc);
return Matrix4.setTranslation(uniformState.modelView, scratchTranslationRtc, mvRtc);
};
},
MODELVIEWPROJECTION : function(uniformState, model) {
return function() {
return uniformState.modelViewProjection;
};
},
MODELINVERSE : function(uniformState, model) {
return function() {
return uniformState.inverseModel;
};
},
VIEWINVERSE : function(uniformState, model) {
return function() {
return uniformState.inverseView;
};
},
PROJECTIONINVERSE : function(uniformState, model) {
return function() {
return uniformState.inverseProjection;
};
},
MODELVIEWINVERSE : function(uniformState, model) {
return function() {
return uniformState.inverseModelView;
};
},
MODELVIEWPROJECTIONINVERSE : function(uniformState, model) {
return function() {
return uniformState.inverseModelViewProjection;
};
},
MODELINVERSETRANSPOSE : function(uniformState, model) {
return function() {
return uniformState.inverseTransposeModel;
};
},
MODELVIEWINVERSETRANSPOSE : function(uniformState, model) {
return function() {
return uniformState.normal;
};
},
VIEWPORT : function(uniformState, model) {
return function() {
return uniformState.viewportCartesian4;
};
}
// JOINTMATRIX created in createCommand()
};
///////////////////////////////////////////////////////////////////////////
function getScalarUniformFunction(value, model) {
var that = {
value : value,
clone : function(source, result) {
return source;
},
func : function() {
return that.value;
}
};
return that;
}
function getVec2UniformFunction(value, model) {
var that = {
value : Cartesian2.fromArray(value),
clone : Cartesian2.clone,
func : function() {
return that.value;
}
};
return that;
}
function getVec3UniformFunction(value, model) {
var that = {
value : Cartesian3.fromArray(value),
clone : Cartesian3.clone,
func : function() {
return that.value;
}
};
return that;
}
function getVec4UniformFunction(value, model) {
var that = {
value : Cartesian4.fromArray(value),
clone : Cartesian4.clone,
func : function() {
return that.value;
}
};
return that;
}
function getMat2UniformFunction(value, model) {
var that = {
value : Matrix2.fromColumnMajorArray(value),
clone : Matrix2.clone,
func : function() {
return that.value;
}
};
return that;
}
function getMat3UniformFunction(value, model) {
var that = {
value : Matrix3.fromColumnMajorArray(value),
clone : Matrix3.clone,
func : function() {
return that.value;
}
};
return that;
}
function getMat4UniformFunction(value, model) {
var that = {
value : Matrix4.fromColumnMajorArray(value),
clone : Matrix4.clone,
func : function() {
return that.value;
}
};
return that;
}
///////////////////////////////////////////////////////////////////////////
function DelayLoadedTextureUniform(value, model) {
this._value = undefined;
this._textureId = value;
this._model = model;
}
defineProperties(DelayLoadedTextureUniform.prototype, {
value : {
get : function() {
// Use the default texture (1x1 white) until the model's texture is loaded
if (!defined(this._value)) {
var texture = this._model._rendererResources.textures[this._textureId];
if (defined(texture)) {
this._value = texture;
} else {
return this._model._defaultTexture;
}
}
return this._value;
},
set : function(value) {
this._value = value;
}
}
});
DelayLoadedTextureUniform.prototype.clone = function(source, result) {
return source;
};
DelayLoadedTextureUniform.prototype.func = undefined;
///////////////////////////////////////////////////////////////////////////
function getTextureUniformFunction(value, model) {
var uniform = new DelayLoadedTextureUniform(value, model);
// Define function here to access closure since 'this' can't be
// used when the Renderer sets uniforms.
uniform.func = function() {
return uniform.value;
};
return uniform;
}
var gltfUniformFunctions = {};
gltfUniformFunctions[WebGLConstants.FLOAT] = getScalarUniformFunction;
gltfUniformFunctions[WebGLConstants.FLOAT_VEC2] = getVec2UniformFunction;
gltfUniformFunctions[WebGLConstants.FLOAT_VEC3] = getVec3UniformFunction;
gltfUniformFunctions[WebGLConstants.FLOAT_VEC4] = getVec4UniformFunction;
gltfUniformFunctions[WebGLConstants.INT] = getScalarUniformFunction;
gltfUniformFunctions[WebGLConstants.INT_VEC2] = getVec2UniformFunction;
gltfUniformFunctions[WebGLConstants.INT_VEC3] = getVec3UniformFunction;
gltfUniformFunctions[WebGLConstants.INT_VEC4] = getVec4UniformFunction;
gltfUniformFunctions[WebGLConstants.BOOL] = getScalarUniformFunction;
gltfUniformFunctions[WebGLConstants.BOOL_VEC2] = getVec2UniformFunction;
gltfUniformFunctions[WebGLConstants.BOOL_VEC3] = getVec3UniformFunction;
gltfUniformFunctions[WebGLConstants.BOOL_VEC4] = getVec4UniformFunction;
gltfUniformFunctions[WebGLConstants.FLOAT_MAT2] = getMat2UniformFunction;
gltfUniformFunctions[WebGLConstants.FLOAT_MAT3] = getMat3UniformFunction;
gltfUniformFunctions[WebGLConstants.FLOAT_MAT4] = getMat4UniformFunction;
gltfUniformFunctions[WebGLConstants.SAMPLER_2D] = getTextureUniformFunction;
// GLTF_SPEC: Support SAMPLER_CUBE. https://github.com/KhronosGroup/glTF/issues/40
var gltfUniformsFromNode = {
MODEL : function(uniformState, model, runtimeNode) {
return function() {
return runtimeNode.computedMatrix;
};
},
VIEW : function(uniformState, model, runtimeNode) {
return function() {
return uniformState.view;
};
},
PROJECTION : function(uniformState, model, runtimeNode) {
return function() {
return uniformState.projection;
};
},
MODELVIEW : function(uniformState, model, runtimeNode) {
var mv = new Matrix4();
return function() {
return Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
};
},
CESIUM_RTC_MODELVIEW : function(uniformState, model, runtimeNode) {
// CESIUM_RTC extension
var mvRtc = new Matrix4();
return function() {
Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvRtc);
return Matrix4.setTranslation(mvRtc, model._rtcCenterEye, mvRtc);
};
},
MODELVIEWPROJECTION : function(uniformState, model, runtimeNode) {
var mvp = new Matrix4();
return function() {
Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvp);
return Matrix4.multiply(uniformState._projection, mvp, mvp);
};
},
MODELINVERSE : function(uniformState, model, runtimeNode) {
var mInverse = new Matrix4();
return function() {
return Matrix4.inverse(runtimeNode.computedMatrix, mInverse);
};
},
VIEWINVERSE : function(uniformState, model) {
return function() {
return uniformState.inverseView;
};
},
PROJECTIONINVERSE : function(uniformState, model, runtimeNode) {
return function() {
return uniformState.inverseProjection;
};
},
MODELVIEWINVERSE : function(uniformState, model, runtimeNode) {
var mv = new Matrix4();
var mvInverse = new Matrix4();
return function() {
Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
return Matrix4.inverse(mv, mvInverse);
};
},
MODELVIEWPROJECTIONINVERSE : function(uniformState, model, runtimeNode) {
var mvp = new Matrix4();
var mvpInverse = new Matrix4();
return function() {
Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvp);
Matrix4.multiply(uniformState._projection, mvp, mvp);
return Matrix4.inverse(mvp, mvpInverse);
};
},
MODELINVERSETRANSPOSE : function(uniformState, model, runtimeNode) {
var mInverse = new Matrix4();
var mInverseTranspose = new Matrix3();
return function() {
Matrix4.inverse(runtimeNode.computedMatrix, mInverse);
Matrix4.getRotation(mInverse, mInverseTranspose);
return Matrix3.transpose(mInverseTranspose, mInverseTranspose);
};
},
MODELVIEWINVERSETRANSPOSE : function(uniformState, model, runtimeNode) {
var mv = new Matrix4();
var mvInverse = new Matrix4();
var mvInverseTranspose = new Matrix3();
return function() {
Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
Matrix4.inverse(mv, mvInverse);
Matrix4.getRotation(mvInverse, mvInverseTranspose);
return Matrix3.transpose(mvInverseTranspose, mvInverseTranspose);
};
},
VIEWPORT : function(uniformState, model, runtimeNode) {
return function() {
return uniformState.viewportCartesian4;
};
}
};
function getUniformFunctionFromSource(source, model, semantic, uniformState) {
var runtimeNode = model._runtime.nodes[source];
return gltfUniformsFromNode[semantic](uniformState, model, runtimeNode);
}
function createUniformMaps(model, context) {
var loadResources = model._loadResources;
if (!loadResources.finishedProgramCreation()) {
return;
}
if (!loadResources.createUniformMaps) {
return;
}
loadResources.createUniformMaps = false;
var gltf = model.gltf;
var materials = gltf.materials;
var techniques = gltf.techniques;
var uniformMaps = model._uniformMaps;
for (var materialId in materials) {
if (materials.hasOwnProperty(materialId)) {
var material = materials[materialId];
var instanceParameters = material.values;
var technique = techniques[material.technique];
var parameters = technique.parameters;
var uniforms = technique.uniforms;
var uniformMap = {};
var uniformValues = {};
var jointMatrixUniformName;
// Uniform parameters
for (var name in uniforms) {
if (uniforms.hasOwnProperty(name)) {
var parameterName = uniforms[name];
var parameter = parameters[parameterName];
// GLTF_SPEC: This does not take into account uniform arrays,
// indicated by parameters with a count property.
//
// https://github.com/KhronosGroup/glTF/issues/258
// GLTF_SPEC: In this implementation, material parameters with a
// semantic or targeted via a source (for animation) are not
// targetable for material animations. Is this too strict?
//
// https://github.com/KhronosGroup/glTF/issues/142
if (defined(instanceParameters[parameterName])) {
// Parameter overrides by the instance technique
var uv = gltfUniformFunctions[parameter.type](instanceParameters[parameterName], model);
uniformMap[name] = uv.func;
uniformValues[parameterName] = uv;
} else if (defined(parameter.node)) {
uniformMap[name] = getUniformFunctionFromSource(parameter.node, model, parameter.semantic, context.uniformState);
} else if (defined(parameter.semantic)) {
if (parameter.semantic !== 'JOINTMATRIX') {
// Map glTF semantic to Cesium automatic uniform
uniformMap[name] = gltfSemanticUniforms[parameter.semantic](context.uniformState, model);
} else {
jointMatrixUniformName = name;
}
} else if (defined(parameter.value)) {
// Technique value that isn't overridden by a material
var uv2 = gltfUniformFunctions[parameter.type](parameter.value, model);
uniformMap[name] = uv2.func;
uniformValues[parameterName] = uv2;
}
}
}
var u = uniformMaps[materialId];
u.uniformMap = uniformMap; // uniform name -> function for the renderer
u.values = uniformValues; // material parameter name -> ModelMaterial for modifying the parameter at runtime
u.jointMatrixUniformName = jointMatrixUniformName;
}
}
}
function scaleFromMatrix5Array(matrix) {
return [matrix[0], matrix[1], matrix[2], matrix[3],
matrix[5], matrix[6], matrix[7], matrix[8],
matrix[10], matrix[11], matrix[12], matrix[13],
matrix[15], matrix[16], matrix[17], matrix[18]];
}
function translateFromMatrix5Array(matrix) {
return [matrix[20], matrix[21], matrix[22], matrix[23]];
}
function createUniformsForQuantizedAttributes(model, primitive, context) {
var gltf = model.gltf;
var accessors = gltf.accessors;
var programId = getProgramForPrimitive(model, primitive);
var quantizedUniforms = model._quantizedUniforms[programId];
var setUniforms = {};
var uniformMap = {};
for (var attribute in primitive.attributes) {
if (primitive.attributes.hasOwnProperty(attribute)) {
var accessorId = primitive.attributes[attribute];
var a = accessors[accessorId];
var extensions = a.extensions;
if (defined(extensions)) {
var quantizedAttributes = extensions.WEB3D_quantized_attributes;
if (defined(quantizedAttributes)) {
var decodeMatrix = quantizedAttributes.decodeMatrix;
var uniformVariable = 'gltf_u_dec_' + attribute.toLowerCase();
switch (a.type) {
case 'SCALAR':
uniformMap[uniformVariable] = getMat2UniformFunction(decodeMatrix, model).func;
setUniforms[uniformVariable] = true;
break;
case 'VEC2':
uniformMap[uniformVariable] = getMat3UniformFunction(decodeMatrix, model).func;
setUniforms[uniformVariable] = true;
break;
case 'VEC3':
uniformMap[uniformVariable] = getMat4UniformFunction(decodeMatrix, model).func;
setUniforms[uniformVariable] = true;
break;
case 'VEC4':
// VEC4 attributes are split into scale and translate because there is no mat5 in GLSL
var uniformVariableScale = uniformVariable + '_scale';
var uniformVariableTranslate = uniformVariable + '_translate';
uniformMap[uniformVariableScale] = getMat4UniformFunction(scaleFromMatrix5Array(decodeMatrix), model).func;
uniformMap[uniformVariableTranslate] = getVec4UniformFunction(translateFromMatrix5Array(decodeMatrix), model).func;
setUniforms[uniformVariableScale] = true;
setUniforms[uniformVariableTranslate] = true;
break;
}
}
}
}
}
// If there are any unset quantized uniforms in this program, they should be set to the identity
for (var quantizedUniform in quantizedUniforms) {
if (quantizedUniforms.hasOwnProperty(quantizedUniform)) {
if (!setUniforms[quantizedUniform]) {
var properties = quantizedUniforms[quantizedUniform];
if (defined(properties.mat)) {
if (properties.mat === 2) {
uniformMap[quantizedUniform] = getMat2UniformFunction(Matrix2.IDENTITY, model).func;
} else if (properties.mat === 3) {
uniformMap[quantizedUniform] = getMat3UniformFunction(Matrix3.IDENTITY, model).func;
} else if (properties.mat === 4) {
uniformMap[quantizedUniform] = getMat4UniformFunction(Matrix4.IDENTITY, model).func;
}
}
if (defined(properties.vec)) {
if (properties.vec === 4) {
uniformMap[quantizedUniform] = getVec4UniformFunction([0, 0, 0, 0], model).func;
}
}
}
}
}
return uniformMap;
}
function createPickColorFunction(color) {
return function() {
return color;
};
}
function createJointMatricesFunction(runtimeNode) {
return function() {
return runtimeNode.computedJointMatrices;
};
}
function createSilhouetteColorFunction(model) {
return function() {
return model.silhouetteColor;
};
}
function createSilhouetteSizeFunction(model) {
return function() {
return model.silhouetteSize;
};
}
function createColorFunction(model) {
return function() {
return model.color;
};
}
function createColorBlendFunction(model) {
return function() {
return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount);
};
}
function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) {
var nodeCommands = model._nodeCommands;
var pickIds = model._pickIds;
var allowPicking = model.allowPicking;
var runtimeMeshesByName = model._runtime.meshesByName;
var resources = model._rendererResources;
var rendererVertexArrays = resources.vertexArrays;
var rendererPrograms = resources.programs;
var rendererPickPrograms = resources.pickPrograms;
var rendererRenderStates = resources.renderStates;
var uniformMaps = model._uniformMaps;
var gltf = model.gltf;
var accessors = gltf.accessors;
var gltfMeshes = gltf.meshes;
var techniques = gltf.techniques;
var materials = gltf.materials;
var meshes = gltfNode.meshes;
var meshesLength = meshes.length;
for (var j = 0; j < meshesLength; ++j) {
var id = meshes[j];
var mesh = gltfMeshes[id];
var primitives = mesh.primitives;
var length = primitives.length;
// The glTF node hierarchy is a DAG so a node can have more than one
// parent, so a node may already have commands. If so, append more
// since they will have a different model matrix.
for (var i = 0; i < length; ++i) {
var primitive = primitives[i];
var ix = accessors[primitive.indices];
var material = materials[primitive.material];
var technique = techniques[material.technique];
var programId = technique.program;
var boundingSphere;
var positionAccessor = primitive.attributes.POSITION;
if (defined(positionAccessor)) {
var minMax = getAccessorMinMax(gltf, positionAccessor);
boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(minMax.min), Cartesian3.fromArray(minMax.max));
}
var vertexArray = rendererVertexArrays[id + '.primitive.' + i];
var offset;
var count;
if (defined(ix)) {
count = ix.count;
offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices
}
else {
var positions = accessors[primitive.attributes.POSITION];
count = positions.count;
offset = 0;
}
var um = uniformMaps[primitive.material];
var uniformMap = um.uniformMap;
if (defined(um.jointMatrixUniformName)) {
var jointUniformMap = {};
jointUniformMap[um.jointMatrixUniformName] = createJointMatricesFunction(runtimeNode);
uniformMap = combine(uniformMap, jointUniformMap);
}
uniformMap = combine(uniformMap, {
gltf_color : createColorFunction(model),
gltf_colorBlend : createColorBlendFunction(model)
});
// Allow callback to modify the uniformMap
if (defined(model._uniformMapLoaded)) {
uniformMap = model._uniformMapLoaded(uniformMap, programId, runtimeNode);
}
// Add uniforms for decoding quantized attributes if used
if (usesExtension(model, 'WEB3D_quantized_attributes')) {
var quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive, context);
uniformMap = combine(uniformMap, quantizedUniformMap);
}
var rs = rendererRenderStates[material.technique];
// GLTF_SPEC: Offical means to determine translucency. https://github.com/KhronosGroup/glTF/issues/105
var isTranslucent = rs.blending.enabled;
var owner = {
primitive : defaultValue(model.pickPrimitive, model),
id : model.id,
node : runtimeNode.publicNode,
mesh : runtimeMeshesByName[mesh.name]
};
var castShadows = ShadowMode.castShadows(model._shadows);
var receiveShadows = ShadowMode.receiveShadows(model._shadows);
var command = new DrawCommand({
boundingVolume : new BoundingSphere(), // updated in update()
cull : model.cull,
modelMatrix : new Matrix4(), // computed in update()
primitiveType : primitive.mode,
vertexArray : vertexArray,
count : count,
offset : offset,
shaderProgram : rendererPrograms[technique.program],
castShadows : castShadows,
receiveShadows : receiveShadows,
uniformMap : uniformMap,
renderState : rs,
owner : owner,
pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE
});
var pickCommand;
if (allowPicking) {
var pickUniformMap;
// Callback to override default model picking
if (defined(model._pickFragmentShaderLoaded)) {
if (defined(model._pickUniformMapLoaded)) {
pickUniformMap = model._pickUniformMapLoaded(uniformMap);
} else {
// This is unlikely, but could happen if the override shader does not
// need new uniforms since, for example, its pick ids are coming from
// a vertex attribute or are baked into the shader source.
pickUniformMap = combine(uniformMap);
}
} else {
var pickId = context.createPickId(owner);
pickIds.push(pickId);
var pickUniforms = {
czm_pickColor : createPickColorFunction(pickId.color)
};
pickUniformMap = combine(uniformMap, pickUniforms);
}
pickCommand = new DrawCommand({
boundingVolume : new BoundingSphere(), // updated in update()
cull : model.cull,
modelMatrix : new Matrix4(), // computed in update()
primitiveType : primitive.mode,
vertexArray : vertexArray,
count : count,
offset : offset,
shaderProgram : rendererPickPrograms[technique.program],
uniformMap : pickUniformMap,
renderState : rs,
owner : owner,
pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE
});
}
var command2D;
var pickCommand2D;
if (!scene3DOnly) {
command2D = DrawCommand.shallowClone(command);
command2D.boundingVolume = new BoundingSphere(); // updated in update()
command2D.modelMatrix = new Matrix4(); // updated in update()
if (allowPicking) {
pickCommand2D = DrawCommand.shallowClone(pickCommand);
pickCommand2D.boundingVolume = new BoundingSphere(); // updated in update()
pickCommand2D.modelMatrix = new Matrix4(); // updated in update()
}
}
var nodeCommand = {
show : true,
boundingSphere : boundingSphere,
command : command,
pickCommand : pickCommand,
command2D : command2D,
pickCommand2D : pickCommand2D,
// Generated on demand when silhouette size is greater than 0.0 and silhouette alpha is greater than 0.0
silhouetteModelCommand : undefined,
silhouetteModelCommand2D : undefined,
silhouetteColorCommand : undefined,
silhouetteColorCommand2D : undefined,
// Generated on demand when color alpha is less than 1.0
translucentCommand : undefined,
translucentCommand2D : undefined
};
runtimeNode.commands.push(nodeCommand);
nodeCommands.push(nodeCommand);
}
}
}
function createRuntimeNodes(model, context, scene3DOnly) {
var loadResources = model._loadResources;
if (!loadResources.finishedEverythingButTextureCreation()) {
return;
}
if (!loadResources.createRuntimeNodes) {
return;
}
loadResources.createRuntimeNodes = false;
var rootNodes = [];
var runtimeNodes = model._runtime.nodes;
var gltf = model.gltf;
var nodes = gltf.nodes;
var scene = gltf.scenes[gltf.scene];
var sceneNodes = scene.nodes;
var length = sceneNodes.length;
var stack = [];
for (var i = 0; i < length; ++i) {
stack.push({
parentRuntimeNode : undefined,
gltfNode : nodes[sceneNodes[i]],
id : sceneNodes[i]
});
while (stack.length > 0) {
var n = stack.pop();
var parentRuntimeNode = n.parentRuntimeNode;
var gltfNode = n.gltfNode;
// Node hierarchy is a DAG so a node can have more than one parent so it may already exist
var runtimeNode = runtimeNodes[n.id];
if (runtimeNode.parents.length === 0) {
if (defined(gltfNode.matrix)) {
runtimeNode.matrix = Matrix4.fromColumnMajorArray(gltfNode.matrix);
} else {
// TRS converted to Cesium types
var rotation = gltfNode.rotation;
runtimeNode.translation = Cartesian3.fromArray(gltfNode.translation);
runtimeNode.rotation = Quaternion.unpack(rotation);
runtimeNode.scale = Cartesian3.fromArray(gltfNode.scale);
}
}
if (defined(parentRuntimeNode)) {
parentRuntimeNode.children.push(runtimeNode);
runtimeNode.parents.push(parentRuntimeNode);
} else {
rootNodes.push(runtimeNode);
}
if (defined(gltfNode.meshes)) {
createCommand(model, gltfNode, runtimeNode, context, scene3DOnly);
}
var children = gltfNode.children;
var childrenLength = children.length;
for (var k = 0; k < childrenLength; ++k) {
stack.push({
parentRuntimeNode : runtimeNode,
gltfNode : nodes[children[k]],
id : children[k]
});
}
}
}
model._runtime.rootNodes = rootNodes;
model._runtime.nodes = runtimeNodes;
}
function createResources(model, frameState) {
var context = frameState.context;
var scene3DOnly = frameState.scene3DOnly;
if (model._loadRendererResourcesFromCache) {
var resources = model._rendererResources;
var cachedResources = model._cachedRendererResources;
resources.buffers = cachedResources.buffers;
resources.vertexArrays = cachedResources.vertexArrays;
resources.programs = cachedResources.programs;
resources.pickPrograms = cachedResources.pickPrograms;
resources.silhouettePrograms = cachedResources.silhouettePrograms;
resources.textures = cachedResources.textures;
resources.samplers = cachedResources.samplers;
resources.renderStates = cachedResources.renderStates;
// Vertex arrays are unique to this model, create instead of using the cache.
if (defined(model._precreatedAttributes)) {
createVertexArrays(model, context);
}
} else {
createBuffers(model, context); // using glTF bufferViews
createPrograms(model, context);
createSamplers(model, context);
loadTexturesFromBufferViews(model);
createTextures(model, context);
}
createSkins(model);
createRuntimeAnimations(model);
if (!model._loadRendererResourcesFromCache) {
createVertexArrays(model, context); // using glTF meshes
createRenderStates(model, context); // using glTF materials/techniques/states
// Long-term, we might not cache render states if they could change
// due to an animation, e.g., a uniform going from opaque to transparent.
// Could use copy-on-write if it is worth it. Probably overkill.
}
createUniformMaps(model, context); // using glTF materials/techniques
createRuntimeNodes(model, context, scene3DOnly); // using glTF scene
}
///////////////////////////////////////////////////////////////////////////
function getNodeMatrix(node, result) {
var publicNode = node.publicNode;
var publicMatrix = publicNode.matrix;
if (publicNode.useMatrix && defined(publicMatrix)) {
// Public matrix overrides orginial glTF matrix and glTF animations
Matrix4.clone(publicMatrix, result);
} else if (defined(node.matrix)) {
Matrix4.clone(node.matrix, result);
} else {
Matrix4.fromTranslationQuaternionRotationScale(node.translation, node.rotation, node.scale, result);
// Keep matrix returned by the node in-sync if the node is targeted by an animation. Only TRS nodes can be targeted.
publicNode.setMatrix(result);
}
}
var scratchNodeStack = [];
var scratchComputedMatrixIn2D = new Matrix4();
function updateNodeHierarchyModelMatrix(model, modelTransformChanged, justLoaded, projection) {
var maxDirtyNumber = model._maxDirtyNumber;
var allowPicking = model.allowPicking;
var rootNodes = model._runtime.rootNodes;
var length = rootNodes.length;
var nodeStack = scratchNodeStack;
var computedModelMatrix = model._computedModelMatrix;
if (model._mode !== SceneMode.SCENE3D) {
computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D);
}
for (var i = 0; i < length; ++i) {
var n = rootNodes[i];
getNodeMatrix(n, n.transformToRoot);
nodeStack.push(n);
while (nodeStack.length > 0) {
n = nodeStack.pop();
var transformToRoot = n.transformToRoot;
var commands = n.commands;
if ((n.dirtyNumber === maxDirtyNumber) || modelTransformChanged || justLoaded) {
var nodeMatrix = Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, n.computedMatrix);
var commandsLength = commands.length;
if (commandsLength > 0) {
// Node has meshes, which has primitives. Update their commands.
for (var j = 0 ; j < commandsLength; ++j) {
var primitiveCommand = commands[j];
var command = primitiveCommand.command;
Matrix4.clone(nodeMatrix, command.modelMatrix);
// PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation)
BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume);
if (defined(model._rtcCenter)) {
Cartesian3.add(model._rtcCenter, command.boundingVolume.center, command.boundingVolume.center);
}
if (allowPicking) {
var pickCommand = primitiveCommand.pickCommand;
Matrix4.clone(command.modelMatrix, pickCommand.modelMatrix);
BoundingSphere.clone(command.boundingVolume, pickCommand.boundingVolume);
}
// If the model crosses the IDL in 2D, it will be drawn in one viewport, but part of it
// will be clipped by the viewport. We create a second command that translates the model
// model matrix to the opposite side of the map so the part that was clipped in one viewport
// is drawn in the other.
command = primitiveCommand.command2D;
if (defined(command) && model._mode === SceneMode.SCENE2D) {
Matrix4.clone(nodeMatrix, command.modelMatrix);
command.modelMatrix[13] -= CesiumMath.sign(command.modelMatrix[13]) * 2.0 * CesiumMath.PI * projection.ellipsoid.maximumRadius;
BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume);
if (allowPicking) {
var pickCommand2D = primitiveCommand.pickCommand2D;
Matrix4.clone(command.modelMatrix, pickCommand2D.modelMatrix);
BoundingSphere.clone(command.boundingVolume, pickCommand2D.boundingVolume);
}
}
}
}
}
var children = n.children;
var childrenLength = children.length;
for (var k = 0; k < childrenLength; ++k) {
var child = children[k];
// A node's transform needs to be updated if
// - It was targeted for animation this frame, or
// - Any of its ancestors were targeted for animation this frame
// PERFORMANCE_IDEA: if a child has multiple parents and only one of the parents
// is dirty, all the subtrees for each child instance will be dirty; we probably
// won't see this in the wild often.
child.dirtyNumber = Math.max(child.dirtyNumber, n.dirtyNumber);
if ((child.dirtyNumber === maxDirtyNumber) || justLoaded) {
// Don't check for modelTransformChanged since if only the model's model matrix changed,
// we do not need to rebuild the local transform-to-root, only the final
// [model's-model-matrix][transform-to-root] above.
getNodeMatrix(child, child.transformToRoot);
Matrix4.multiplyTransformation(transformToRoot, child.transformToRoot, child.transformToRoot);
}
nodeStack.push(child);
}
}
}
++model._maxDirtyNumber;
}
var scratchObjectSpace = new Matrix4();
function applySkins(model) {
var skinnedNodes = model._runtime.skinnedNodes;
var length = skinnedNodes.length;
for (var i = 0; i < length; ++i) {
var node = skinnedNodes[i];
scratchObjectSpace = Matrix4.inverseTransformation(node.transformToRoot, scratchObjectSpace);
var computedJointMatrices = node.computedJointMatrices;
var joints = node.joints;
var bindShapeMatrix = node.bindShapeMatrix;
var inverseBindMatrices = node.inverseBindMatrices;
var inverseBindMatricesLength = inverseBindMatrices.length;
for (var m = 0; m < inverseBindMatricesLength; ++m) {
// [joint-matrix] = [node-to-root^-1][joint-to-root][inverse-bind][bind-shape]
if (!defined(computedJointMatrices[m])) {
computedJointMatrices[m] = new Matrix4();
}
computedJointMatrices[m] = Matrix4.multiplyTransformation(scratchObjectSpace, joints[m].transformToRoot, computedJointMatrices[m]);
computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], inverseBindMatrices[m], computedJointMatrices[m]);
if (defined(bindShapeMatrix)) {
// Optimization for when bind shape matrix is the identity.
computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], bindShapeMatrix, computedJointMatrices[m]);
}
}
}
}
function updatePerNodeShow(model) {
// Totally not worth it, but we could optimize this:
// http://blogs.agi.com/insight3d/index.php/2008/02/13/deletion-in-bounding-volume-hierarchies/
var rootNodes = model._runtime.rootNodes;
var length = rootNodes.length;
var nodeStack = scratchNodeStack;
for (var i = 0; i < length; ++i) {
var n = rootNodes[i];
n.computedShow = n.publicNode.show;
nodeStack.push(n);
while (nodeStack.length > 0) {
n = nodeStack.pop();
var show = n.computedShow;
var nodeCommands = n.commands;
var nodeCommandsLength = nodeCommands.length;
for (var j = 0 ; j < nodeCommandsLength; ++j) {
nodeCommands[j].show = show;
}
// if commandsLength is zero, the node has a light or camera
var children = n.children;
var childrenLength = children.length;
for (var k = 0; k < childrenLength; ++k) {
var child = children[k];
// Parent needs to be shown for child to be shown.
child.computedShow = show && child.publicNode.show;
nodeStack.push(child);
}
}
}
}
function updatePickIds(model, context) {
var id = model.id;
if (model._id !== id) {
model._id = id;
var pickIds = model._pickIds;
var length = pickIds.length;
for (var i = 0; i < length; ++i) {
pickIds[i].object.id = id;
}
}
}
function updateWireframe(model) {
if (model._debugWireframe !== model.debugWireframe) {
model._debugWireframe = model.debugWireframe;
// This assumes the original primitive was TRIANGLES and that the triangles
// are connected for the wireframe to look perfect.
var primitiveType = model.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES;
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
for (var i = 0; i < length; ++i) {
nodeCommands[i].command.primitiveType = primitiveType;
}
}
}
function updateShowBoundingVolume(model) {
if (model.debugShowBoundingVolume !== model._debugShowBoundingVolume) {
model._debugShowBoundingVolume = model.debugShowBoundingVolume;
var debugShowBoundingVolume = model.debugShowBoundingVolume;
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
for (var i = 0; i < length; ++i) {
nodeCommands[i].command.debugShowBoundingVolume = debugShowBoundingVolume;
}
}
}
function updateShadows(model) {
if (model.shadows !== model._shadows) {
model._shadows = model.shadows;
var castShadows = ShadowMode.castShadows(model.shadows);
var receiveShadows = ShadowMode.receiveShadows(model.shadows);
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
for (var i = 0; i < length; i++) {
var nodeCommand = nodeCommands[i];
nodeCommand.command.castShadows = castShadows;
nodeCommand.command.receiveShadows = receiveShadows;
}
}
}
function getTranslucentRenderState(renderState) {
var rs = clone(renderState, true);
rs.cull.enabled = false;
rs.depthTest.enabled = true;
rs.depthMask = false;
rs.blending = BlendingState.ALPHA_BLEND;
return RenderState.fromCache(rs);
}
function deriveTranslucentCommand(command) {
var translucentCommand = DrawCommand.shallowClone(command);
translucentCommand.pass = Pass.TRANSLUCENT;
translucentCommand.renderState = getTranslucentRenderState(command.renderState);
return translucentCommand;
}
function updateColor(model, frameState) {
// Generate translucent commands when the blend color has an alpha in the range (0.0, 1.0) exclusive
var scene3DOnly = frameState.scene3DOnly;
var alpha = model.color.alpha;
if ((alpha > 0.0) && (alpha < 1.0)) {
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
if (!defined(nodeCommands[0].translucentCommand)) {
for (var i = 0; i < length; ++i) {
var nodeCommand = nodeCommands[i];
var command = nodeCommand.command;
nodeCommand.translucentCommand = deriveTranslucentCommand(command);
if (!scene3DOnly) {
var command2D = nodeCommand.command2D;
nodeCommand.translucentCommand2D = deriveTranslucentCommand(command2D);
}
}
}
}
}
function getProgramId(model, program) {
var programs = model._rendererResources.programs;
for (var id in programs) {
if (programs.hasOwnProperty(id)) {
if (programs[id] === program) {
return id;
}
}
}
}
function createSilhouetteProgram(model, program, frameState) {
var vs = program.vertexShaderSource.sources[0];
var attributeLocations = program._attributeLocations;
var normalAttributeName = model._normalAttributeName;
// Modified from http://forum.unity3d.com/threads/toon-outline-but-with-diffuse-surface.24668/
vs = ShaderSource.replaceMain(vs, 'gltf_silhouette_main');
vs +=
'uniform float gltf_silhouetteSize; \n' +
'void main() \n' +
'{ \n' +
' gltf_silhouette_main(); \n' +
' vec3 n = normalize(czm_normal3D * ' + normalAttributeName + '); \n' +
' n.x *= czm_projection[0][0]; \n' +
' n.y *= czm_projection[1][1]; \n' +
' vec4 clip = gl_Position; \n' +
' clip.xy += n.xy * clip.w * gltf_silhouetteSize / czm_viewport.z; \n' +
' gl_Position = clip; \n' +
'}';
var fs =
'uniform vec4 gltf_silhouetteColor; \n' +
'void main() \n' +
'{ \n' +
' gl_FragColor = gltf_silhouetteColor; \n' +
'}';
return ShaderProgram.fromCache({
context : frameState.context,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
}
function hasSilhouette(model, frameState) {
return silhouetteSupported(frameState.context) && (model.silhouetteSize > 0.0) && (model.silhouetteColor.alpha > 0.0) && defined(model._normalAttributeName);
}
function hasTranslucentCommands(model) {
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
for (var i = 0; i < length; ++i) {
var nodeCommand = nodeCommands[i];
var command = nodeCommand.command;
if (command.pass === Pass.TRANSLUCENT) {
return true;
}
}
return false;
}
function isTranslucent(model) {
return (model.color.alpha > 0.0) && (model.color.alpha < 1.0);
}
function isInvisible(model) {
return (model.color.alpha === 0.0);
}
function alphaDirty(currAlpha, prevAlpha) {
// Returns whether the alpha state has changed between invisible, translucent, or opaque
return (Math.floor(currAlpha) !== Math.floor(prevAlpha)) || (Math.ceil(currAlpha) !== Math.ceil(prevAlpha));
}
var silhouettesLength = 0;
function createSilhouetteCommands(model, frameState) {
// Wrap around after exceeding the 8-bit stencil limit.
// The reference is unique to each model until this point.
var stencilReference = (++silhouettesLength) % 255;
// If the model is translucent the silhouette needs to be in the translucent pass.
// Otherwise the silhouette would be rendered before the model.
var silhouetteTranslucent = hasTranslucentCommands(model) || isTranslucent(model) || (model.silhouetteColor.alpha < 1.0);
var silhouettePrograms = model._rendererResources.silhouettePrograms;
var scene3DOnly = frameState.scene3DOnly;
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
for (var i = 0; i < length; ++i) {
var nodeCommand = nodeCommands[i];
var command = nodeCommand.command;
// Create model command
var modelCommand = isTranslucent(model) ? nodeCommand.translucentCommand : command;
var silhouetteModelCommand = DrawCommand.shallowClone(modelCommand);
var renderState = clone(modelCommand.renderState);
// Write the reference value into the stencil buffer.
renderState.stencilTest = {
enabled : true,
frontFunction : WebGLConstants.ALWAYS,
backFunction : WebGLConstants.ALWAYS,
reference : stencilReference,
mask : ~0,
frontOperation : {
fail : WebGLConstants.KEEP,
zFail : WebGLConstants.KEEP,
zPass : WebGLConstants.REPLACE
},
backOperation : {
fail : WebGLConstants.KEEP,
zFail : WebGLConstants.KEEP,
zPass : WebGLConstants.REPLACE
}
};
if (isInvisible(model)) {
// When the model is invisible disable color and depth writes but still write into the stencil buffer
renderState.colorMask = {
red : false,
green : false,
blue : false,
alpha : false
};
renderState.depthMask = false;
}
renderState = RenderState.fromCache(renderState);
silhouetteModelCommand.renderState = renderState;
nodeCommand.silhouetteModelCommand = silhouetteModelCommand;
// Create color command
var silhouetteColorCommand = DrawCommand.shallowClone(command);
renderState = clone(command.renderState, true);
renderState.depthTest.enabled = true;
renderState.cull.enabled = false;
if (silhouetteTranslucent) {
silhouetteColorCommand.pass = Pass.TRANSLUCENT;
renderState.depthMask = false;
renderState.blending = BlendingState.ALPHA_BLEND;
}
// Only render silhouette if the value in the stencil buffer equals the reference
renderState.stencilTest = {
enabled : true,
frontFunction : WebGLConstants.NOTEQUAL,
backFunction : WebGLConstants.NOTEQUAL,
reference : stencilReference,
mask : ~0,
frontOperation : {
fail : WebGLConstants.KEEP,
zFail : WebGLConstants.KEEP,
zPass : WebGLConstants.KEEP
},
backOperation : {
fail : WebGLConstants.KEEP,
zFail : WebGLConstants.KEEP,
zPass : WebGLConstants.KEEP
}
};
renderState = RenderState.fromCache(renderState);
// If the silhouette program has already been cached use it
var program = command.shaderProgram;
var id = getProgramId(model, program);
var silhouetteProgram = silhouettePrograms[id];
if (!defined(silhouetteProgram)) {
silhouetteProgram = createSilhouetteProgram(model, program, frameState);
silhouettePrograms[id] = silhouetteProgram;
}
var silhouetteUniformMap = combine(command.uniformMap, {
gltf_silhouetteColor : createSilhouetteColorFunction(model),
gltf_silhouetteSize : createSilhouetteSizeFunction(model)
});
silhouetteColorCommand.renderState = renderState;
silhouetteColorCommand.shaderProgram = silhouetteProgram;
silhouetteColorCommand.uniformMap = silhouetteUniformMap;
silhouetteColorCommand.castShadows = false;
silhouetteColorCommand.receiveShadows = false;
nodeCommand.silhouetteColorCommand = silhouetteColorCommand;
if (!scene3DOnly) {
var command2D = nodeCommand.command2D;
var silhouetteModelCommand2D = DrawCommand.shallowClone(silhouetteModelCommand);
silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume;
silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix;
nodeCommand.silhouetteModelCommand2D = silhouetteModelCommand2D;
var silhouetteColorCommand2D = DrawCommand.shallowClone(silhouetteColorCommand);
silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume;
silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix;
nodeCommand.silhouetteColorCommand2D = silhouetteColorCommand2D;
}
}
}
function updateSilhouette(model, frameState) {
// Generate silhouette commands when the silhouette size is greater than 0.0 and the alpha is greater than 0.0
// There are two silhouette commands:
// 1. silhouetteModelCommand : render model normally while enabling stencil mask
// 2. silhouetteColorCommand : render enlarged model with a solid color while enabling stencil tests
if (!hasSilhouette(model, frameState)) {
return;
}
var nodeCommands = model._nodeCommands;
var dirty = alphaDirty(model.color.alpha, model._color.alpha) ||
alphaDirty(model.silhouetteColor.alpha, model._silhouetteColor.alpha) ||
!defined(nodeCommands[0].silhouetteModelCommand);
Color.clone(model.color, model._color);
Color.clone(model.silhouetteColor, model._silhouetteColor);
if (dirty) {
createSilhouetteCommands(model, frameState);
}
}
var scratchBoundingSphere = new BoundingSphere();
function scaleInPixels(positionWC, radius, frameState) {
scratchBoundingSphere.center = positionWC;
scratchBoundingSphere.radius = radius;
return frameState.camera.getPixelSize(scratchBoundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
}
var scratchPosition = new Cartesian3();
var scratchCartographic = new Cartographic();
function getScale(model, frameState) {
var scale = model.scale;
if (model.minimumPixelSize !== 0.0) {
// Compute size of bounding sphere in pixels
var context = frameState.context;
var maxPixelSize = Math.max(context.drawingBufferWidth, context.drawingBufferHeight);
var m = defined(model._clampedModelMatrix) ? model._clampedModelMatrix : model.modelMatrix;
scratchPosition.x = m[12];
scratchPosition.y = m[13];
scratchPosition.z = m[14];
if (defined(model._rtcCenter)) {
Cartesian3.add(model._rtcCenter, scratchPosition, scratchPosition);
}
if (model._mode !== SceneMode.SCENE3D) {
var projection = frameState.mapProjection;
var cartographic = projection.ellipsoid.cartesianToCartographic(scratchPosition, scratchCartographic);
projection.project(cartographic, scratchPosition);
Cartesian3.fromElements(scratchPosition.z, scratchPosition.x, scratchPosition.y, scratchPosition);
}
var radius = model.boundingSphere.radius;
var metersPerPixel = scaleInPixels(scratchPosition, radius, frameState);
// metersPerPixel is always > 0.0
var pixelsPerMeter = 1.0 / metersPerPixel;
var diameterInPixels = Math.min(pixelsPerMeter * (2.0 * radius), maxPixelSize);
// Maintain model's minimum pixel size
if (diameterInPixels < model.minimumPixelSize) {
scale = (model.minimumPixelSize * metersPerPixel) / (2.0 * model._initialRadius);
}
}
return defined(model.maximumScale) ? Math.min(model.maximumScale, scale) : scale;
}
function releaseCachedGltf(model) {
if (defined(model._cacheKey) && defined(model._cachedGltf) && (--model._cachedGltf.count === 0)) {
delete gltfCache[model._cacheKey];
}
model._cachedGltf = undefined;
}
function checkSupportedExtensions(model) {
var extensionsUsed = model.gltf.extensionsUsed;
if (defined(extensionsUsed)) {
var extensionsUsedCount = extensionsUsed.length;
for (var index=0;index= nearSquared) && (distance2 <= farSquared);
}
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {RuntimeError} Failed to load external reference.
*/
Model.prototype.update = function(frameState) {
if (frameState.mode === SceneMode.MORPHING) {
return;
}
var context = frameState.context;
this._defaultTexture = context.defaultTexture;
if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) {
// Use renderer resources from cache instead of loading/creating them?
var cachedRendererResources;
var cacheKey = this.cacheKey;
if (defined(cacheKey)) {
context.cache.modelRendererResourceCache = defaultValue(context.cache.modelRendererResourceCache, {});
var modelCaches = context.cache.modelRendererResourceCache;
cachedRendererResources = modelCaches[this.cacheKey];
if (defined(cachedRendererResources)) {
if (!cachedRendererResources.ready) {
// Cached resources for the model are not loaded yet. We'll
// try again every frame until they are.
return;
}
++cachedRendererResources.count;
this._loadRendererResourcesFromCache = true;
} else {
cachedRendererResources = new CachedRendererResources(context, cacheKey);
cachedRendererResources.count = 1;
modelCaches[this.cacheKey] = cachedRendererResources;
}
this._cachedRendererResources = cachedRendererResources;
} else {
cachedRendererResources = new CachedRendererResources(context);
cachedRendererResources.count = 1;
this._cachedRendererResources = cachedRendererResources;
}
this._state = ModelState.LOADING;
this._boundingSphere = computeBoundingSphere(this.gltf);
this._initialRadius = this._boundingSphere.radius;
checkSupportedExtensions(this);
if (this._state !== ModelState.FAILED) {
var extensions = this.gltf.extensions;
if (defined(extensions) && defined(extensions.CESIUM_RTC)) {
this._rtcCenter = Cartesian3.fromArray(extensions.CESIUM_RTC.center);
this._rtcCenterEye = new Cartesian3();
}
this._loadResources = new LoadResources();
parse(this);
}
}
var loadResources = this._loadResources;
var incrementallyLoadTextures = this._incrementallyLoadTextures;
var justLoaded = false;
if (this._state === ModelState.LOADING) {
// Create WebGL resources as buffers/shaders/textures are downloaded
createResources(this, frameState);
// Transition from LOADING -> LOADED once resources are downloaded and created.
// Textures may continue to stream in while in the LOADED state.
if (loadResources.finished() ||
(incrementallyLoadTextures && loadResources.finishedEverythingButTextureCreation())) {
this._state = ModelState.LOADED;
justLoaded = true;
}
}
// Incrementally stream textures.
if (defined(loadResources) && (this._state === ModelState.LOADED)) {
// Also check justLoaded so we don't process twice during the transition frame
if (incrementallyLoadTextures && !justLoaded) {
createResources(this, frameState);
}
if (loadResources.finished()) {
this._loadResources = undefined; // Clear CPU memory since WebGL resources were created.
var resources = this._rendererResources;
var cachedResources = this._cachedRendererResources;
cachedResources.buffers = resources.buffers;
cachedResources.vertexArrays = resources.vertexArrays;
cachedResources.programs = resources.programs;
cachedResources.pickPrograms = resources.pickPrograms;
cachedResources.silhouettePrograms = resources.silhouettePrograms;
cachedResources.textures = resources.textures;
cachedResources.samplers = resources.samplers;
cachedResources.renderStates = resources.renderStates;
cachedResources.ready = true;
// The normal attribute name is required for silhouettes, so get it before the gltf JSON is released
this._normalAttributeName = getAttributeOrUniformBySemantic(this.gltf, 'NORMAL');
// Vertex arrays are unique to this model, do not store in cache.
if (defined(this._precreatedAttributes)) {
cachedResources.vertexArrays = {};
}
if (this.releaseGltfJson) {
releaseCachedGltf(this);
}
}
}
var silhouette = hasSilhouette(this, frameState);
var translucent = isTranslucent(this);
var invisible = isInvisible(this);
var displayConditionPassed = defined(this.distanceDisplayCondition) ? distanceDisplayConditionVisible(this, frameState) : true;
var show = this.show && displayConditionPassed && (this.scale !== 0.0) && (!invisible || silhouette);
if ((show && this._state === ModelState.LOADED) || justLoaded) {
var animated = this.activeAnimations.update(frameState) || this._cesiumAnimationsDirty;
this._cesiumAnimationsDirty = false;
this._dirty = false;
var modelMatrix = this.modelMatrix;
var modeChanged = frameState.mode !== this._mode;
this._mode = frameState.mode;
// Model's model matrix needs to be updated
var modelTransformChanged = !Matrix4.equals(this._modelMatrix, modelMatrix) ||
(this._scale !== this.scale) ||
(this._minimumPixelSize !== this.minimumPixelSize) || (this.minimumPixelSize !== 0.0) || // Minimum pixel size changed or is enabled
(this._maximumScale !== this.maximumScale) ||
(this._heightReference !== this.heightReference) || this._heightChanged ||
modeChanged;
if (modelTransformChanged || justLoaded) {
Matrix4.clone(modelMatrix, this._modelMatrix);
updateClamping(this);
if (defined(this._clampedModelMatrix)) {
modelMatrix = this._clampedModelMatrix;
}
this._scale = this.scale;
this._minimumPixelSize = this.minimumPixelSize;
this._maximumScale = this.maximumScale;
this._heightReference = this.heightReference;
this._heightChanged = false;
var scale = getScale(this, frameState);
var computedModelMatrix = this._computedModelMatrix;
Matrix4.multiplyByUniformScale(modelMatrix, scale, computedModelMatrix);
Matrix4.multiplyTransformation(computedModelMatrix, yUpToZUp, computedModelMatrix);
}
// Update modelMatrix throughout the graph as needed
if (animated || modelTransformChanged || justLoaded) {
updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection);
this._dirty = true;
if (animated || justLoaded) {
// Apply skins if animation changed any node transforms
applySkins(this);
}
}
if (this._perNodeShowDirty) {
this._perNodeShowDirty = false;
updatePerNodeShow(this);
}
updatePickIds(this, context);
updateWireframe(this);
updateShowBoundingVolume(this);
updateShadows(this);
updateColor(this, frameState);
updateSilhouette(this, frameState);
}
if (justLoaded) {
// Called after modelMatrix update.
var model = this;
frameState.afterRender.push(function() {
model._ready = true;
model._readyPromise.resolve(model);
});
return;
}
// We don't check show at the top of the function since we
// want to be able to progressively load models when they are not shown,
// and then have them visible immediately when show is set to true.
if (show && !this._ignoreCommands) {
// PERFORMANCE_IDEA: This is terrible
var commandList = frameState.commandList;
var passes = frameState.passes;
var nodeCommands = this._nodeCommands;
var length = nodeCommands.length;
var i;
var nc;
var idl2D = frameState.mapProjection.ellipsoid.maximumRadius * CesiumMath.PI;
var boundingVolume;
if (passes.render) {
for (i = 0; i < length; ++i) {
nc = nodeCommands[i];
if (nc.show) {
var command = translucent ? nc.translucentCommand : nc.command;
command = silhouette ? nc.silhouetteModelCommand : command;
commandList.push(command);
boundingVolume = nc.command.boundingVolume;
if (frameState.mode === SceneMode.SCENE2D &&
(boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
var command2D = translucent ? nc.translucentCommand2D : nc.command2D;
command2D = silhouette ? nc.silhouetteModelCommand2D : command2D;
commandList.push(command2D);
}
}
}
if (silhouette) {
// Render second silhouette pass
for (i = 0; i < length; ++i) {
nc = nodeCommands[i];
if (nc.show) {
commandList.push(nc.silhouetteColorCommand);
boundingVolume = nc.command.boundingVolume;
if (frameState.mode === SceneMode.SCENE2D &&
(boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
commandList.push(nc.silhouetteColorCommand2D);
}
}
}
}
}
if (passes.pick && this.allowPicking) {
for (i = 0; i < length; ++i) {
nc = nodeCommands[i];
if (nc.show) {
var pickCommand = nc.pickCommand;
commandList.push(pickCommand);
boundingVolume = pickCommand.boundingVolume;
if (frameState.mode === SceneMode.SCENE2D &&
(boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
commandList.push(nc.pickCommand2D);
}
}
}
}
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see Model#destroy
*/
Model.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* model = model && model.destroy();
*
* @see Model#isDestroyed
*/
Model.prototype.destroy = function() {
// Vertex arrays are unique to this model, destroy here.
if (defined(this._precreatedAttributes)) {
destroy(this._rendererResources.vertexArrays);
}
this._rendererResources = undefined;
this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release();
var pickIds = this._pickIds;
var length = pickIds.length;
for (var i = 0; i < length; ++i) {
pickIds[i].destroy();
}
releaseCachedGltf(this);
return destroyObject(this);
};
return Model;
});
/*global define*/
define('DataSources/ModelVisualizer',[
'../Core/AssociativeArray',
'../Core/BoundingSphere',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Matrix4',
'../Scene/ColorBlendMode',
'../Scene/HeightReference',
'../Scene/Model',
'../Scene/ModelAnimationLoop',
'../Scene/ShadowMode',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
BoundingSphere,
Color,
defined,
destroyObject,
DeveloperError,
Matrix4,
ColorBlendMode,
HeightReference,
Model,
ModelAnimationLoop,
ShadowMode,
BoundingSphereState,
Property) {
'use strict';
var defaultScale = 1.0;
var defaultMinimumPixelSize = 0.0;
var defaultIncrementallyLoadTextures = true;
var defaultShadows = ShadowMode.ENABLED;
var defaultHeightReference = HeightReference.NONE;
var defaultSilhouetteColor = Color.RED;
var defaultSilhouetteSize = 0.0;
var defaultColor = Color.WHITE;
var defaultColorBlendMode = ColorBlendMode.HIGHLIGHT;
var defaultColorBlendAmount = 0.5;
var color = new Color();
var modelMatrixScratch = new Matrix4();
var nodeMatrixScratch = new Matrix4();
/**
* A {@link Visualizer} which maps {@link Entity#model} to a {@link Model}.
* @alias ModelVisualizer
* @constructor
*
* @param {Scene} scene The scene the primitives will be rendered in.
* @param {EntityCollection} entityCollection The entityCollection to visualize.
*/
function ModelVisualizer(scene, entityCollection) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(entityCollection)) {
throw new DeveloperError('entityCollection is required.');
}
entityCollection.collectionChanged.addEventListener(ModelVisualizer.prototype._onCollectionChanged, this);
this._scene = scene;
this._primitives = scene.primitives;
this._entityCollection = entityCollection;
this._modelHash = {};
this._entitiesToVisualize = new AssociativeArray();
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
}
/**
* Updates models created this visualizer to match their
* Entity counterpart at the given time.
*
* @param {JulianDate} time The time to update to.
* @returns {Boolean} This function always returns true.
*/
ModelVisualizer.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var entities = this._entitiesToVisualize.values;
var modelHash = this._modelHash;
var primitives = this._primitives;
for (var i = 0, len = entities.length; i < len; i++) {
var entity = entities[i];
var modelGraphics = entity._model;
var uri;
var modelData = modelHash[entity.id];
var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(modelGraphics._show, time, true);
var modelMatrix;
if (show) {
modelMatrix = entity._getModelMatrix(time, modelMatrixScratch);
uri = Property.getValueOrUndefined(modelGraphics._uri, time);
show = defined(modelMatrix) && defined(uri);
}
if (!show) {
if (defined(modelData)) {
modelData.modelPrimitive.show = false;
}
continue;
}
var model = defined(modelData) ? modelData.modelPrimitive : undefined;
if (!defined(model) || uri !== modelData.uri) {
if (defined(model)) {
primitives.removeAndDestroy(model);
delete modelHash[entity.id];
}
model = Model.fromGltf({
url : uri,
incrementallyLoadTextures : Property.getValueOrDefault(modelGraphics._incrementallyLoadTextures, time, defaultIncrementallyLoadTextures),
scene : this._scene
});
model.readyPromise.otherwise(onModelError);
model.id = entity;
primitives.add(model);
modelData = {
modelPrimitive : model,
uri : uri,
animationsRunning : false,
nodeTransformationsScratch : {},
originalNodeMatrixHash : {}
};
modelHash[entity.id] = modelData;
}
model.show = true;
model.scale = Property.getValueOrDefault(modelGraphics._scale, time, defaultScale);
model.minimumPixelSize = Property.getValueOrDefault(modelGraphics._minimumPixelSize, time, defaultMinimumPixelSize);
model.maximumScale = Property.getValueOrUndefined(modelGraphics._maximumScale, time);
model.modelMatrix = Matrix4.clone(modelMatrix, model.modelMatrix);
model.shadows = Property.getValueOrDefault(modelGraphics._shadows, time, defaultShadows);
model.heightReference = Property.getValueOrDefault(modelGraphics._heightReference, time, defaultHeightReference);
model.distanceDisplayCondition = Property.getValueOrUndefined(modelGraphics._distanceDisplayCondition, time);
model.silhouetteColor = Property.getValueOrDefault(modelGraphics.silhouetteColor, time, defaultSilhouetteColor);
model.silhouetteSize = Property.getValueOrDefault(modelGraphics.silhouetteSize, time, defaultSilhouetteSize);
model.color = Property.getValueOrDefault(modelGraphics._color, time, defaultColor, color);
model.colorBlendMode = Property.getValueOrDefault(modelGraphics._colorBlendMode, time, defaultColorBlendMode);
model.colorBlendAmount = Property.getValueOrDefault(modelGraphics._colorBlendAmount, time, defaultColorBlendAmount);
if (model.ready) {
var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true);
if (modelData.animationsRunning !== runAnimations) {
if (runAnimations) {
model.activeAnimations.addAll({
loop : ModelAnimationLoop.REPEAT
});
} else {
model.activeAnimations.removeAll();
}
modelData.animationsRunning = runAnimations;
}
// Apply node transformations
var nodeTransformations = Property.getValueOrUndefined(modelGraphics._nodeTransformations, time, modelData.nodeTransformationsScratch);
if (defined(nodeTransformations)) {
var originalNodeMatrixHash = modelData.originalNodeMatrixHash;
var nodeNames = Object.keys(nodeTransformations);
for (var nodeIndex = 0, nodeLength = nodeNames.length; nodeIndex < nodeLength; ++nodeIndex) {
var nodeName = nodeNames[nodeIndex];
var nodeTransformation = nodeTransformations[nodeName];
if (!defined(nodeTransformation)) {
continue;
}
var modelNode = model.getNode(nodeName);
if (!defined(modelNode)) {
continue;
}
var originalNodeMatrix = originalNodeMatrixHash[nodeName];
if (!defined(originalNodeMatrix)) {
originalNodeMatrix = modelNode.matrix.clone();
originalNodeMatrixHash[nodeName] = originalNodeMatrix;
}
var transformationMatrix = Matrix4.fromTranslationRotationScale(nodeTransformation, nodeMatrixScratch);
modelNode.matrix = Matrix4.multiply(originalNodeMatrix, transformationMatrix, transformationMatrix);
}
}
}
}
return true;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
ModelVisualizer.prototype.isDestroyed = function() {
return false;
};
/**
* Removes and destroys all primitives created by this instance.
*/
ModelVisualizer.prototype.destroy = function() {
this._entityCollection.collectionChanged.removeEventListener(ModelVisualizer.prototype._onCollectionChanged, this);
var entities = this._entitiesToVisualize.values;
var modelHash = this._modelHash;
var primitives = this._primitives;
for (var i = entities.length - 1; i > -1; i--) {
removeModel(this, entities[i], modelHash, primitives);
}
return destroyObject(this);
};
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
ModelVisualizer.prototype.getBoundingSphere = function(entity, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var modelData = this._modelHash[entity.id];
if (!defined(modelData)) {
return BoundingSphereState.FAILED;
}
var model = modelData.modelPrimitive;
if (!defined(model) || !model.show) {
return BoundingSphereState.FAILED;
}
if (!model.ready) {
return BoundingSphereState.PENDING;
}
if (model.heightReference === HeightReference.NONE) {
BoundingSphere.transform(model.boundingSphere, model.modelMatrix, result);
} else {
if (!defined(model._clampedModelMatrix)) {
return BoundingSphereState.PENDING;
}
BoundingSphere.transform(model.boundingSphere, model._clampedModelMatrix, result);
}
return BoundingSphereState.DONE;
};
/**
* @private
*/
ModelVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
var i;
var entity;
var entities = this._entitiesToVisualize;
var modelHash = this._modelHash;
var primitives = this._primitives;
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
if (defined(entity._model) && defined(entity._position)) {
entities.set(entity.id, entity);
}
}
for (i = changed.length - 1; i > -1; i--) {
entity = changed[i];
if (defined(entity._model) && defined(entity._position)) {
clearNodeTransformationsScratch(entity, modelHash);
entities.set(entity.id, entity);
} else {
removeModel(this, entity, modelHash, primitives);
entities.remove(entity.id);
}
}
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
removeModel(this, entity, modelHash, primitives);
entities.remove(entity.id);
}
};
function removeModel(visualizer, entity, modelHash, primitives) {
var modelData = modelHash[entity.id];
if (defined(modelData)) {
primitives.removeAndDestroy(modelData.modelPrimitive);
delete modelHash[entity.id];
}
}
function clearNodeTransformationsScratch(entity, modelHash) {
var modelData = modelHash[entity.id];
if (defined(modelData)) {
modelData.nodeTransformationsScratch = {};
}
}
function onModelError(error) {
console.error(error);
}
return ModelVisualizer;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PolylineCommon',[],function() {
'use strict';
return "void clipLineSegmentToNearPlane(\n\
vec3 p0,\n\
vec3 p1,\n\
out vec4 positionWC,\n\
out bool clipped,\n\
out bool culledByNearPlane)\n\
{\n\
culledByNearPlane = false;\n\
clipped = false;\n\
vec3 p1ToP0 = p1 - p0;\n\
float magnitude = length(p1ToP0);\n\
vec3 direction = normalize(p1ToP0);\n\
float endPoint0Distance = -(czm_currentFrustum.x + p0.z);\n\
float denominator = -direction.z;\n\
if (endPoint0Distance < 0.0 && abs(denominator) < czm_epsilon7)\n\
{\n\
culledByNearPlane = true;\n\
}\n\
else if (endPoint0Distance < 0.0 && abs(denominator) > czm_epsilon7)\n\
{\n\
float t = (czm_currentFrustum.x + p0.z) / denominator;\n\
if (t < 0.0 || t > magnitude)\n\
{\n\
culledByNearPlane = true;\n\
}\n\
else\n\
{\n\
p0 = p0 + t * direction;\n\
clipped = true;\n\
}\n\
}\n\
positionWC = czm_eyeToWindowCoordinates(vec4(p0, 1.0));\n\
}\n\
vec4 getPolylineWindowCoordinates(vec4 position, vec4 previous, vec4 next, float expandDirection, float width, bool usePrevious) {\n\
vec4 endPointWC, p0, p1;\n\
bool culledByNearPlane, clipped;\n\
vec4 positionEC = czm_modelViewRelativeToEye * position;\n\
vec4 prevEC = czm_modelViewRelativeToEye * previous;\n\
vec4 nextEC = czm_modelViewRelativeToEye * next;\n\
clipLineSegmentToNearPlane(prevEC.xyz, positionEC.xyz, p0, clipped, culledByNearPlane);\n\
clipLineSegmentToNearPlane(nextEC.xyz, positionEC.xyz, p1, clipped, culledByNearPlane);\n\
clipLineSegmentToNearPlane(positionEC.xyz, usePrevious ? prevEC.xyz : nextEC.xyz, endPointWC, clipped, culledByNearPlane);\n\
if (culledByNearPlane)\n\
{\n\
return vec4(0.0, 0.0, 0.0, 1.0);\n\
}\n\
vec2 prevWC = normalize(p0.xy - endPointWC.xy);\n\
vec2 nextWC = normalize(p1.xy - endPointWC.xy);\n\
float expandWidth = width * 0.5;\n\
vec2 direction;\n\
if (czm_equalsEpsilon(previous.xyz - position.xyz, vec3(0.0), czm_epsilon1) || czm_equalsEpsilon(prevWC, -nextWC, czm_epsilon1))\n\
{\n\
direction = vec2(-nextWC.y, nextWC.x);\n\
}\n\
else if (czm_equalsEpsilon(next.xyz - position.xyz, vec3(0.0), czm_epsilon1) || clipped)\n\
{\n\
direction = vec2(prevWC.y, -prevWC.x);\n\
}\n\
else\n\
{\n\
vec2 normal = vec2(-nextWC.y, nextWC.x);\n\
direction = normalize((nextWC + prevWC) * 0.5);\n\
if (dot(direction, normal) < 0.0)\n\
{\n\
direction = -direction;\n\
}\n\
float sinAngle = abs(direction.x * nextWC.y - direction.y * nextWC.x);\n\
expandWidth = clamp(expandWidth / sinAngle, 0.0, width * 2.0);\n\
}\n\
vec2 offset = direction * expandDirection * expandWidth * czm_resolutionScale;\n\
return vec4(endPointWC.xy + offset, -endPointWC.z, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PolylineFS',[],function() {
'use strict';
return "varying vec2 v_st;\n\
void main()\n\
{\n\
czm_materialInput materialInput;\n\
materialInput.s = v_st.s;\n\
materialInput.st = v_st;\n\
materialInput.str = vec3(v_st, 0.0);\n\
czm_material material = czm_getMaterial(materialInput);\n\
gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PolylineVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 position2DHigh;\n\
attribute vec3 position2DLow;\n\
attribute vec3 prevPosition3DHigh;\n\
attribute vec3 prevPosition3DLow;\n\
attribute vec3 prevPosition2DHigh;\n\
attribute vec3 prevPosition2DLow;\n\
attribute vec3 nextPosition3DHigh;\n\
attribute vec3 nextPosition3DLow;\n\
attribute vec3 nextPosition2DHigh;\n\
attribute vec3 nextPosition2DLow;\n\
attribute vec4 texCoordExpandAndBatchIndex;\n\
varying vec2 v_st;\n\
varying float v_width;\n\
varying vec4 czm_pickColor;\n\
void main()\n\
{\n\
float texCoord = texCoordExpandAndBatchIndex.x;\n\
float expandDir = texCoordExpandAndBatchIndex.y;\n\
bool usePrev = texCoordExpandAndBatchIndex.z < 0.0;\n\
float batchTableIndex = texCoordExpandAndBatchIndex.w;\n\
vec2 widthAndShow = batchTable_getWidthAndShow(batchTableIndex);\n\
float width = widthAndShow.x + 0.5;\n\
float show = widthAndShow.y;\n\
if (width < 1.0)\n\
{\n\
show = 0.0;\n\
}\n\
vec4 pickColor = batchTable_getPickColor(batchTableIndex);\n\
vec4 p, prev, next;\n\
if (czm_morphTime == 1.0)\n\
{\n\
p = czm_translateRelativeToEye(position3DHigh.xyz, position3DLow.xyz);\n\
prev = czm_translateRelativeToEye(prevPosition3DHigh.xyz, prevPosition3DLow.xyz);\n\
next = czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz);\n\
}\n\
else if (czm_morphTime == 0.0)\n\
{\n\
p = czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy);\n\
prev = czm_translateRelativeToEye(prevPosition2DHigh.zxy, prevPosition2DLow.zxy);\n\
next = czm_translateRelativeToEye(nextPosition2DHigh.zxy, nextPosition2DLow.zxy);\n\
}\n\
else\n\
{\n\
p = czm_columbusViewMorph(\n\
czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy),\n\
czm_translateRelativeToEye(position3DHigh.xyz, position3DLow.xyz),\n\
czm_morphTime);\n\
prev = czm_columbusViewMorph(\n\
czm_translateRelativeToEye(prevPosition2DHigh.zxy, prevPosition2DLow.zxy),\n\
czm_translateRelativeToEye(prevPosition3DHigh.xyz, prevPosition3DLow.xyz),\n\
czm_morphTime);\n\
next = czm_columbusViewMorph(\n\
czm_translateRelativeToEye(nextPosition2DHigh.zxy, nextPosition2DLow.zxy),\n\
czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz),\n\
czm_morphTime);\n\
}\n\
#ifdef DISTANCE_DISPLAY_CONDITION\n\
vec3 centerHigh = batchTable_getCenterHigh(batchTableIndex);\n\
vec4 centerLowAndRadius = batchTable_getCenterLowAndRadius(batchTableIndex);\n\
vec3 centerLow = centerLowAndRadius.xyz;\n\
float radius = centerLowAndRadius.w;\n\
vec2 distanceDisplayCondition = batchTable_getDistanceDisplayCondition(batchTableIndex);\n\
float lengthSq;\n\
if (czm_sceneMode == czm_sceneMode2D)\n\
{\n\
lengthSq = czm_eyeHeight2D.y;\n\
}\n\
else\n\
{\n\
vec4 center = czm_translateRelativeToEye(centerHigh.xyz, centerLow.xyz);\n\
lengthSq = max(0.0, dot(center.xyz, center.xyz) - radius * radius);\n\
}\n\
float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x;\n\
float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y;\n\
if (lengthSq < nearSq || lengthSq > farSq)\n\
{\n\
show = 0.0;\n\
}\n\
#endif\n\
vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev);\n\
gl_Position = czm_viewportOrthographic * positionWC * show;\n\
v_st = vec2(texCoord, clamp(expandDir, 0.0, 1.0));\n\
v_width = width;\n\
czm_pickColor = pickColor;\n\
}\n\
";
});
/*global define*/
define('Scene/Polyline',[
'../Core/arrayRemoveDuplicates',
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/Matrix4',
'../Core/PolylinePipeline',
'./Material'
], function(
arrayRemoveDuplicates,
BoundingSphere,
Cartesian3,
Color,
defaultValue,
defined,
defineProperties,
DeveloperError,
DistanceDisplayCondition,
Matrix4,
PolylinePipeline,
Material) {
'use strict';
/**
* A renderable polyline. Create this by calling {@link PolylineCollection#add}
*
* @alias Polyline
* @internalConstructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.show=true] true
if this polyline will be shown; otherwise, false
.
* @param {Number} [options.width=1.0] The width of the polyline in pixels.
* @param {Boolean} [options.loop=false] Whether a line segment will be added between the last and first line positions to make this line a loop.
* @param {Material} [options.material=Material.ColorType] The material.
* @param {Cartesian3[]} [options.positions] The positions.
* @param {Object} [options.id] The user-defined object to be returned when this polyline is picked.
* @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this polyline will be displayed.
*
* @see PolylineCollection
*
*/
function Polyline(options, polylineCollection) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._show = defaultValue(options.show, true);
this._width = defaultValue(options.width, 1.0);
this._loop = defaultValue(options.loop, false);
this._distanceDisplayCondition = options.distanceDisplayCondition;
this._material = options.material;
if (!defined(this._material)) {
this._material = Material.fromType(Material.ColorType, {
color : new Color(1.0, 1.0, 1.0, 1.0)
});
}
var positions = options.positions;
if (!defined(positions)) {
positions = [];
}
this._positions = positions;
this._actualPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
if (this._loop && this._actualPositions.length > 2) {
if (this._actualPositions === this._positions) {
this._actualPositions = positions.slice();
}
this._actualPositions.push(Cartesian3.clone(this._actualPositions[0]));
}
this._length = this._actualPositions.length;
this._id = options.id;
var modelMatrix;
if (defined(polylineCollection)) {
modelMatrix = Matrix4.clone(polylineCollection.modelMatrix);
}
this._modelMatrix = modelMatrix;
this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix);
this._actualLength = undefined;
this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
this._polylineCollection = polylineCollection;
this._dirty = false;
this._pickId = undefined;
this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions);
this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, this._modelMatrix);
this._boundingVolume2D = new BoundingSphere(); // modified in PolylineCollection
}
var POSITION_INDEX = Polyline.POSITION_INDEX = 0;
var SHOW_INDEX = Polyline.SHOW_INDEX = 1;
var WIDTH_INDEX = Polyline.WIDTH_INDEX = 2;
var MATERIAL_INDEX = Polyline.MATERIAL_INDEX = 3;
var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX = 4;
var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION = 5;
var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES = 6;
function makeDirty(polyline, propertyChanged) {
++polyline._propertiesChanged[propertyChanged];
var polylineCollection = polyline._polylineCollection;
if (defined(polylineCollection)) {
polylineCollection._updatePolyline(polyline, propertyChanged);
polyline._dirty = true;
}
}
defineProperties(Polyline.prototype, {
/**
* Determines if this polyline will be shown. Use this to hide or show a polyline, instead
* of removing it and re-adding it to the collection.
* @memberof Polyline.prototype
* @type {Boolean}
*/
show: {
get: function() {
return this._show;
},
set: function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (value !== this._show) {
this._show = value;
makeDirty(this, SHOW_INDEX);
}
}
},
/**
* Gets or sets the positions of the polyline.
* @memberof Polyline.prototype
* @type {Cartesian3[]}
* @example
* polyline.positions = Cesium.Cartesian3.fromDegreesArray([
* 0.0, 0.0,
* 10.0, 0.0,
* 0.0, 20.0
* ]);
*/
positions : {
get: function() {
return this._positions;
},
set: function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var positions = arrayRemoveDuplicates(value, Cartesian3.equalsEpsilon);
if (this._loop && positions.length > 2) {
if (positions === value) {
positions = value.slice();
}
positions.push(Cartesian3.clone(positions[0]));
}
if (this._actualPositions.length !== positions.length || this._actualPositions.length !== this._length) {
makeDirty(this, POSITION_SIZE_INDEX);
}
this._positions = value;
this._actualPositions = positions;
this._length = positions.length;
this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions, this._boundingVolume);
this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, this._modelMatrix, this._boundingVolumeWC);
makeDirty(this, POSITION_INDEX);
this.update();
}
},
/**
* Gets or sets the surface appearance of the polyline. This can be one of several built-in {@link Material} objects or a custom material, scripted with
* {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
* @memberof Polyline.prototype
* @type {Material}
*/
material: {
get: function() {
return this._material;
},
set: function(material) {
if (!defined(material)) {
throw new DeveloperError('material is required.');
}
if (this._material !== material) {
this._material = material;
makeDirty(this, MATERIAL_INDEX);
}
}
},
/**
* Gets or sets the width of the polyline.
* @memberof Polyline.prototype
* @type {Number}
*/
width: {
get: function() {
return this._width;
},
set: function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
var width = this._width;
if (value !== width) {
this._width = value;
makeDirty(this, WIDTH_INDEX);
}
}
},
/**
* Gets or sets whether a line segment will be added between the first and last polyline positions.
* @memberof Polyline.prototype
* @type {Boolean}
*/
loop: {
get: function() {
return this._loop;
},
set: function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
if (value !== this._loop) {
var positions = this._actualPositions;
if (value) {
if (positions.length > 2 && !Cartesian3.equals(positions[0], positions[positions.length - 1])) {
if (positions.length === this._positions.length) {
this._actualPositions = positions = this._positions.slice();
}
positions.push(Cartesian3.clone(positions[0]));
}
} else {
if (positions.length > 2 && Cartesian3.equals(positions[0], positions[positions.length - 1])) {
if (positions.length - 1 === this._positions.length) {
this._actualPositions = this._positions;
} else {
positions.pop();
}
}
}
this._loop = value;
makeDirty(this, POSITION_SIZE_INDEX);
}
}
},
/**
* Gets or sets the user-defined object returned when the polyline is picked.
* @memberof Polyline.prototype
* @type {Object}
*/
id : {
get : function() {
return this._id;
},
set : function(value) {
this._id = value;
if (defined(this._pickId)) {
this._pickId.object.id = value;
}
}
},
/**
* Gets or sets the condition specifying at what distance from the camera that this polyline will be displayed.
* @memberof Polyline.prototype
* @type {DistanceDisplayCondition}
* @default undefined
*/
distanceDisplayCondition : {
get : function() {
return this._distanceDisplayCondition;
},
set : function(value) {
if (defined(value) && value.far <= value.near) {
throw new DeveloperError('far distance must be greater than near distance.');
}
if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
makeDirty(this, DISTANCE_DISPLAY_CONDITION);
}
}
}
});
/**
* @private
*/
Polyline.prototype.update = function() {
var modelMatrix = Matrix4.IDENTITY;
if (defined(this._polylineCollection)) {
modelMatrix = this._polylineCollection.modelMatrix;
}
var segmentPositionsLength = this._segments.positions.length;
var segmentLengths = this._segments.lengths;
var positionsChanged = this._propertiesChanged[POSITION_INDEX] > 0 || this._propertiesChanged[POSITION_SIZE_INDEX] > 0;
if (!Matrix4.equals(modelMatrix, this._modelMatrix) || positionsChanged) {
this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix);
this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, modelMatrix, this._boundingVolumeWC);
}
this._modelMatrix = modelMatrix;
if (this._segments.positions.length !== segmentPositionsLength) {
// number of positions changed
makeDirty(this, POSITION_SIZE_INDEX);
} else {
var length = segmentLengths.length;
for (var i = 0; i < length; ++i) {
if (segmentLengths[i] !== this._segments.lengths[i]) {
// indices changed
makeDirty(this, POSITION_SIZE_INDEX);
break;
}
}
}
};
/**
* @private
*/
Polyline.prototype.getPickId = function(context) {
if (!defined(this._pickId)) {
this._pickId = context.createPickId({
primitive : this,
collection : this._polylineCollection,
id : this._id
});
}
return this._pickId;
};
Polyline.prototype._clean = function() {
this._dirty = false;
var properties = this._propertiesChanged;
for ( var k = 0; k < NUMBER_OF_PROPERTIES - 1; ++k) {
properties[k] = 0;
}
};
Polyline.prototype._destroy = function() {
this._pickId = this._pickId && this._pickId.destroy();
this._material = this._material && this._material.destroy();
this._polylineCollection = undefined;
};
return Polyline;
});
/*global define*/
define('Scene/PolylineCollection',[
'../Core/BoundingSphere',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/Color',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EncodedCartesian3',
'../Core/IndexDatatype',
'../Core/Intersect',
'../Core/Math',
'../Core/Matrix4',
'../Core/Plane',
'../Core/RuntimeError',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/ContextLimits',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArray',
'../Shaders/PolylineCommon',
'../Shaders/PolylineFS',
'../Shaders/PolylineVS',
'./BatchTable',
'./BlendingState',
'./Material',
'./Polyline',
'./SceneMode'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
Color,
ComponentDatatype,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EncodedCartesian3,
IndexDatatype,
Intersect,
CesiumMath,
Matrix4,
Plane,
RuntimeError,
Buffer,
BufferUsage,
ContextLimits,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
ShaderSource,
VertexArray,
PolylineCommon,
PolylineFS,
PolylineVS,
BatchTable,
BlendingState,
Material,
Polyline,
SceneMode) {
'use strict';
var SHOW_INDEX = Polyline.SHOW_INDEX;
var WIDTH_INDEX = Polyline.WIDTH_INDEX;
var POSITION_INDEX = Polyline.POSITION_INDEX;
var MATERIAL_INDEX = Polyline.MATERIAL_INDEX;
//POSITION_SIZE_INDEX is needed for when the polyline's position array changes size.
//When it does, we need to recreate the indicesBuffer.
var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX;
var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION;
var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES;
var attributeLocations = {
texCoordExpandAndBatchIndex : 0,
position3DHigh : 1,
position3DLow : 2,
position2DHigh : 3,
position2DLow : 4,
prevPosition3DHigh : 5,
prevPosition3DLow : 6,
prevPosition2DHigh : 7,
prevPosition2DLow : 8,
nextPosition3DHigh : 9,
nextPosition3DLow : 10,
nextPosition2DHigh : 11,
nextPosition2DLow : 12
};
/**
* A renderable collection of polylines.
*
*
*
* Example polylines
*
*
* Polylines are added and removed from the collection using {@link PolylineCollection#add}
* and {@link PolylineCollection#remove}.
*
* @alias PolylineCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each polyline from model to world coordinates.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
*
* @performance For best performance, prefer a few collections, each with many polylines, to
* many collections with only a few polylines each. Organize collections so that polylines
* with the same update frequency are in the same collection, i.e., polylines that do not
* change should be in one collection; polylines that change every frame should be in another
* collection; and so on.
*
* @see PolylineCollection#add
* @see PolylineCollection#remove
* @see Polyline
* @see LabelCollection
*
* @example
* // Create a polyline collection with two polylines
* var polylines = new Cesium.PolylineCollection();
* polylines.add({
* positions : Cesium.Cartesian3.fromDegreesArray([
* -75.10, 39.57,
* -77.02, 38.53,
* -80.50, 35.14,
* -80.12, 25.46]),
* width : 2
* });
*
* polylines.add({
* positions : Cesium.Cartesian3.fromDegreesArray([
* -73.10, 37.57,
* -75.02, 36.53,
* -78.50, 33.14,
* -78.12, 23.46]),
* width : 4
* });
*/
function PolylineCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The 4x4 transformation matrix that transforms each polyline in this collection from model to world coordinates.
* When this is the identity matrix, the polylines are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
this._opaqueRS = undefined;
this._translucentRS = undefined;
this._colorCommands = [];
this._pickCommands = [];
this._polylinesUpdated = false;
this._polylinesRemoved = false;
this._createVertexArray = false;
this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
this._polylines = [];
this._polylineBuckets = {};
// The buffer usage is determined based on the usage of the attribute over time.
this._positionBufferUsage = { bufferUsage : BufferUsage.STATIC_DRAW, frameCount : 0 };
this._mode = undefined;
this._polylinesToUpdate = [];
this._vertexArrays = [];
this._positionBuffer = undefined;
this._texCoordExpandAndBatchIndexBuffer = undefined;
this._batchTable = undefined;
this._createBatchTable = false;
}
defineProperties(PolylineCollection.prototype, {
/**
* Returns the number of polylines in this collection. This is commonly used with
* {@link PolylineCollection#get} to iterate over all the polylines
* in the collection.
* @memberof PolylineCollection.prototype
* @type {Number}
*/
length : {
get : function() {
removePolylines(this);
return this._polylines.length;
}
}
});
/**
* Creates and adds a polyline with the specified initial properties to the collection.
* The added polyline is returned so it can be modified or removed from the collection later.
*
* @param {Object}[polyline] A template describing the polyline's properties as shown in Example 1.
* @returns {Polyline} The polyline that was added to the collection.
*
* @performance After calling add
, {@link PolylineCollection#update} is called and
* the collection's vertex buffer is rewritten - an O(n)
operation that also incurs CPU to GPU overhead.
* For best performance, add as many polylines as possible before calling update
.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Example 1: Add a polyline, specifying all the default values.
* var p = polylines.add({
* show : true,
* positions : ellipsoid.cartographicArrayToCartesianArray([
Cesium.Cartographic.fromDegrees(-75.10, 39.57),
Cesium.Cartographic.fromDegrees(-77.02, 38.53)]),
* width : 1
* });
*
* @see PolylineCollection#remove
* @see PolylineCollection#removeAll
* @see PolylineCollection#update
*/
PolylineCollection.prototype.add = function(polyline) {
var p = new Polyline(polyline, this);
p._index = this._polylines.length;
this._polylines.push(p);
this._createVertexArray = true;
this._createBatchTable = true;
return p;
};
/**
* Removes a polyline from the collection.
*
* @param {Polyline} polyline The polyline to remove.
* @returns {Boolean} true
if the polyline was removed; false
if the polyline was not found in the collection.
*
* @performance After calling remove
, {@link PolylineCollection#update} is called and
* the collection's vertex buffer is rewritten - an O(n)
operation that also incurs CPU to GPU overhead.
* For best performance, remove as many polylines as possible before calling update
.
* If you intend to temporarily hide a polyline, it is usually more efficient to call
* {@link Polyline#show} instead of removing and re-adding the polyline.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* var p = polylines.add(...);
* polylines.remove(p); // Returns true
*
* @see PolylineCollection#add
* @see PolylineCollection#removeAll
* @see PolylineCollection#update
* @see Polyline#show
*/
PolylineCollection.prototype.remove = function(polyline) {
if (this.contains(polyline)) {
this._polylines[polyline._index] = undefined; // Removed later
this._polylinesRemoved = true;
this._createVertexArray = true;
this._createBatchTable = true;
if (defined(polyline._bucket)) {
var bucket = polyline._bucket;
bucket.shaderProgram = bucket.shaderProgram && bucket.shaderProgram.destroy();
bucket.pickShaderProgram = bucket.pickShaderProgram && bucket.pickShaderProgram.destroy();
}
polyline._destroy();
return true;
}
return false;
};
/**
* Removes all polylines from the collection.
*
* @performance O(n)
. It is more efficient to remove all the polylines
* from a collection and then add new ones than to create a new collection entirely.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* polylines.add(...);
* polylines.add(...);
* polylines.removeAll();
*
* @see PolylineCollection#add
* @see PolylineCollection#remove
* @see PolylineCollection#update
*/
PolylineCollection.prototype.removeAll = function() {
releaseShaders(this);
destroyPolylines(this);
this._polylineBuckets = {};
this._polylinesRemoved = false;
this._polylines.length = 0;
this._polylinesToUpdate.length = 0;
this._createVertexArray = true;
};
/**
* Determines if this collection contains the specified polyline.
*
* @param {Polyline} polyline The polyline to check for.
* @returns {Boolean} true if this collection contains the polyline, false otherwise.
*
* @see PolylineCollection#get
*/
PolylineCollection.prototype.contains = function(polyline) {
return defined(polyline) && polyline._polylineCollection === this;
};
/**
* Returns the polyline in the collection at the specified index. Indices are zero-based
* and increase as polylines are added. Removing a polyline shifts all polylines after
* it to the left, changing their indices. This function is commonly used with
* {@link PolylineCollection#length} to iterate over all the polylines
* in the collection.
*
* @param {Number} index The zero-based index of the polyline.
* @returns {Polyline} The polyline at the specified index.
*
* @performance If polylines were removed from the collection and
* {@link PolylineCollection#update} was not called, an implicit O(n)
* operation is performed.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* // Toggle the show property of every polyline in the collection
* var len = polylines.length;
* for (var i = 0; i < len; ++i) {
* var p = polylines.get(i);
* p.show = !p.show;
* }
*
* @see PolylineCollection#length
*/
PolylineCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
removePolylines(this);
return this._polylines[index];
};
function createBatchTable(collection, context) {
if (defined(collection._batchTable)) {
collection._batchTable.destroy();
}
var attributes = [{
functionName : 'batchTable_getWidthAndShow',
componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
componentsPerAttribute : 2
}, {
functionName : 'batchTable_getPickColor',
componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
componentsPerAttribute : 4,
normalize : true
}, {
functionName : 'batchTable_getCenterHigh',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3
}, {
functionName : 'batchTable_getCenterLowAndRadius',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 4
}, {
functionName : 'batchTable_getDistanceDisplayCondition',
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2
}];
collection._batchTable = new BatchTable(context, attributes, collection._polylines.length);
}
var scratchUpdatePolylineEncodedCartesian = new EncodedCartesian3();
var scratchUpdatePolylineCartesian4 = new Cartesian4();
var scratchNearFarCartesian2 = new Cartesian2();
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.
*/
PolylineCollection.prototype.update = function(frameState) {
removePolylines(this);
if (this._polylines.length === 0) {
return;
}
updateMode(this, frameState);
var context = frameState.context;
var projection = frameState.mapProjection;
var polyline;
var properties = this._propertiesChanged;
if (this._createBatchTable) {
if (ContextLimits.maximumVertexTextureImageUnits === 0) {
throw new RuntimeError('Vertex texture fetch support is required to render polylines. The maximum number of vertex texture image units must be greater than zero.');
}
createBatchTable(this, context);
this._createBatchTable = false;
}
if (this._createVertexArray || computeNewBuffersUsage(this)) {
createVertexArrays(this, context, projection);
} else if (this._polylinesUpdated) {
// Polylines were modified, but no polylines were added or removed.
var polylinesToUpdate = this._polylinesToUpdate;
if (this._mode !== SceneMode.SCENE3D) {
var updateLength = polylinesToUpdate.length;
for ( var i = 0; i < updateLength; ++i) {
polyline = polylinesToUpdate[i];
polyline.update();
}
}
// if a polyline's positions size changes, we need to recreate the vertex arrays and vertex buffers because the indices will be different.
// if a polyline's material changes, we need to recreate the VAOs and VBOs because they will be batched differently.
if (properties[POSITION_SIZE_INDEX] || properties[MATERIAL_INDEX]) {
createVertexArrays(this, context, projection);
} else {
var length = polylinesToUpdate.length;
var polylineBuckets = this._polylineBuckets;
for ( var ii = 0; ii < length; ++ii) {
polyline = polylinesToUpdate[ii];
properties = polyline._propertiesChanged;
var bucket = polyline._bucket;
var index = 0;
for (var x in polylineBuckets) {
if (polylineBuckets.hasOwnProperty(x)) {
if (polylineBuckets[x] === bucket) {
if (properties[POSITION_INDEX]) {
bucket.writeUpdate(index, polyline, this._positionBuffer, projection);
}
break;
}
index += polylineBuckets[x].lengthOfPositions;
}
}
if (properties[SHOW_INDEX] || properties[WIDTH_INDEX]) {
this._batchTable.setBatchedAttribute(polyline._index, 0, new Cartesian2(polyline._width, polyline._show));
}
if (this._batchTable.attributes.length > 2) {
if (properties[POSITION_INDEX] || properties[POSITION_SIZE_INDEX]) {
var boundingSphere = frameState.mode === SceneMode.SCENE2D ? polyline._boundingVolume2D : polyline._boundingVolumeWC;
var encodedCenter = EncodedCartesian3.fromCartesian(boundingSphere.center, scratchUpdatePolylineEncodedCartesian);
var low = Cartesian4.fromElements(encodedCenter.low.x, encodedCenter.low.y, encodedCenter.low.z, boundingSphere.radius, scratchUpdatePolylineCartesian4);
this._batchTable.setBatchedAttribute(polyline._index, 2, encodedCenter.high);
this._batchTable.setBatchedAttribute(polyline._index, 3, low);
}
if (properties[DISTANCE_DISPLAY_CONDITION]) {
var nearFarCartesian = scratchNearFarCartesian2;
nearFarCartesian.x = 0.0;
nearFarCartesian.y = Number.MAX_VALUE;
var distanceDisplayCondition = polyline.distanceDisplayCondition;
if (defined(distanceDisplayCondition)) {
nearFarCartesian.x = distanceDisplayCondition.near;
nearFarCartesian.x = distanceDisplayCondition.far;
}
this._batchTable.setBatchedAttribute(polyline._index, 4, nearFarCartesian);
}
}
polyline._clean();
}
}
polylinesToUpdate.length = 0;
this._polylinesUpdated = false;
}
properties = this._propertiesChanged;
for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
properties[k] = 0;
}
var modelMatrix = Matrix4.IDENTITY;
if (frameState.mode === SceneMode.SCENE3D) {
modelMatrix = this.modelMatrix;
}
var pass = frameState.passes;
var useDepthTest = (frameState.morphTime !== 0.0);
if (!defined(this._opaqueRS) || this._opaqueRS.depthTest.enabled !== useDepthTest) {
this._opaqueRS = RenderState.fromCache({
depthMask : useDepthTest,
depthTest : {
enabled : useDepthTest
}
});
}
if (!defined(this._translucentRS) || this._translucentRS.depthTest.enabled !== useDepthTest) {
this._translucentRS = RenderState.fromCache({
blending : BlendingState.ALPHA_BLEND,
depthMask : !useDepthTest,
depthTest : {
enabled : useDepthTest
}
});
}
this._batchTable.update(frameState);
if (pass.render) {
var colorList = this._colorCommands;
createCommandLists(this, frameState, colorList, modelMatrix, true);
}
if (pass.pick) {
var pickList = this._pickCommands;
createCommandLists(this, frameState, pickList, modelMatrix, false);
}
};
var boundingSphereScratch = new BoundingSphere();
var boundingSphereScratch2 = new BoundingSphere();
function createCommandLists(polylineCollection, frameState, commands, modelMatrix, renderPass) {
var context = frameState.context;
var commandList = frameState.commandList;
var commandsLength = commands.length;
var commandIndex = 0;
var cloneBoundingSphere = true;
var vertexArrays = polylineCollection._vertexArrays;
var debugShowBoundingVolume = polylineCollection.debugShowBoundingVolume;
var batchTable = polylineCollection._batchTable;
var uniformCallback = batchTable.getUniformMapCallback();
var length = vertexArrays.length;
for ( var m = 0; m < length; ++m) {
var va = vertexArrays[m];
var buckets = va.buckets;
var bucketLength = buckets.length;
for ( var n = 0; n < bucketLength; ++n) {
var bucketLocator = buckets[n];
var offset = bucketLocator.offset;
var sp = renderPass ? bucketLocator.bucket.shaderProgram : bucketLocator.bucket.pickShaderProgram;
var polylines = bucketLocator.bucket.polylines;
var polylineLength = polylines.length;
var currentId;
var currentMaterial;
var count = 0;
var command;
for (var s = 0; s < polylineLength; ++s) {
var polyline = polylines[s];
var mId = createMaterialId(polyline._material);
if (mId !== currentId) {
if (defined(currentId) && count > 0) {
var translucent = currentMaterial.isTranslucent();
if (commandIndex >= commandsLength) {
command = new DrawCommand({
owner : polylineCollection
});
commands.push(command);
} else {
command = commands[commandIndex];
}
++commandIndex;
command.boundingVolume = BoundingSphere.clone(boundingSphereScratch, command.boundingVolume);
command.modelMatrix = modelMatrix;
command.shaderProgram = sp;
command.vertexArray = va.va;
command.renderState = translucent ? polylineCollection._translucentRS : polylineCollection._opaqueRS;
command.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
command.debugShowBoundingVolume = renderPass ? debugShowBoundingVolume : false;
command.uniformMap = uniformCallback(currentMaterial._uniforms);
command.count = count;
command.offset = offset;
offset += count;
count = 0;
cloneBoundingSphere = true;
commandList.push(command);
}
currentMaterial = polyline._material;
currentMaterial.update(context);
currentId = mId;
}
var locators = polyline._locatorBuckets;
var locatorLength = locators.length;
for (var t = 0; t < locatorLength; ++t) {
var locator = locators[t];
if (locator.locator === bucketLocator) {
count += locator.count;
}
}
var boundingVolume;
if (frameState.mode === SceneMode.SCENE3D) {
boundingVolume = polyline._boundingVolumeWC;
} else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
boundingVolume = polyline._boundingVolume2D;
} else if (frameState.mode === SceneMode.SCENE2D) {
if (defined(polyline._boundingVolume2D)) {
boundingVolume = BoundingSphere.clone(polyline._boundingVolume2D, boundingSphereScratch2);
boundingVolume.center.x = 0.0;
}
} else if (defined(polyline._boundingVolumeWC) && defined(polyline._boundingVolume2D)) {
boundingVolume = BoundingSphere.union(polyline._boundingVolumeWC, polyline._boundingVolume2D, boundingSphereScratch2);
}
if (cloneBoundingSphere) {
cloneBoundingSphere = false;
BoundingSphere.clone(boundingVolume, boundingSphereScratch);
} else {
BoundingSphere.union(boundingVolume, boundingSphereScratch, boundingSphereScratch);
}
}
if (defined(currentId) && count > 0) {
if (commandIndex >= commandsLength) {
command = new DrawCommand({
owner : polylineCollection
});
commands.push(command);
} else {
command = commands[commandIndex];
}
++commandIndex;
command.boundingVolume = BoundingSphere.clone(boundingSphereScratch, command.boundingVolume);
command.modelMatrix = modelMatrix;
command.shaderProgram = sp;
command.vertexArray = va.va;
command.renderState = currentMaterial.isTranslucent() ? polylineCollection._translucentRS : polylineCollection._opaqueRS;
command.pass = currentMaterial.isTranslucent() ? Pass.TRANSLUCENT : Pass.OPAQUE;
command.debugShowBoundingVolume = renderPass ? debugShowBoundingVolume : false;
command.uniformMap = uniformCallback(currentMaterial._uniforms);
command.count = count;
command.offset = offset;
cloneBoundingSphere = true;
commandList.push(command);
}
currentId = undefined;
}
}
commands.length = commandIndex;
}
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see PolylineCollection#destroy
*/
PolylineCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* polylines = polylines && polylines.destroy();
*
* @see PolylineCollection#isDestroyed
*/
PolylineCollection.prototype.destroy = function() {
destroyVertexArrays(this);
releaseShaders(this);
destroyPolylines(this);
this._batchTable = this._batchTable && this._batchTable.destroy();
return destroyObject(this);
};
function computeNewBuffersUsage(collection) {
var usageChanged = false;
var properties = collection._propertiesChanged;
var bufferUsage = collection._positionBufferUsage;
if (properties[POSITION_INDEX]) {
if (bufferUsage.bufferUsage !== BufferUsage.STREAM_DRAW) {
usageChanged = true;
bufferUsage.bufferUsage = BufferUsage.STREAM_DRAW;
bufferUsage.frameCount = 100;
} else {
bufferUsage.frameCount = 100;
}
} else {
if (bufferUsage.bufferUsage !== BufferUsage.STATIC_DRAW) {
if (bufferUsage.frameCount === 0) {
usageChanged = true;
bufferUsage.bufferUsage = BufferUsage.STATIC_DRAW;
} else {
bufferUsage.frameCount--;
}
}
}
return usageChanged;
}
var emptyVertexBuffer = [0.0, 0.0, 0.0];
function createVertexArrays(collection, context, projection) {
collection._createVertexArray = false;
releaseShaders(collection);
destroyVertexArrays(collection);
sortPolylinesIntoBuckets(collection);
//stores all of the individual indices arrays.
var totalIndices = [[]];
var indices = totalIndices[0];
var batchTable = collection._batchTable;
//used to determine the vertexBuffer offset if the indicesArray goes over 64k.
//if it's the same polyline while it goes over 64k, the offset needs to backtrack componentsPerAttribute * componentDatatype bytes
//so that the polyline looks contiguous.
//if the polyline ends at the 64k mark, then the offset is just 64k * componentsPerAttribute * componentDatatype
var vertexBufferOffset = [0];
var offset = 0;
var vertexArrayBuckets = [[]];
var totalLength = 0;
var polylineBuckets = collection._polylineBuckets;
var x;
var bucket;
for (x in polylineBuckets) {
if (polylineBuckets.hasOwnProperty(x)) {
bucket = polylineBuckets[x];
bucket.updateShader(context, batchTable);
totalLength += bucket.lengthOfPositions;
}
}
if (totalLength > 0) {
var mode = collection._mode;
var positionArray = new Float32Array(6 * totalLength * 3);
var texCoordExpandAndBatchIndexArray = new Float32Array(totalLength * 4);
var position3DArray;
var positionIndex = 0;
var colorIndex = 0;
var texCoordExpandAndBatchIndexIndex = 0;
for (x in polylineBuckets) {
if (polylineBuckets.hasOwnProperty(x)) {
bucket = polylineBuckets[x];
bucket.write(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection);
if (mode === SceneMode.MORPHING) {
if (!defined(position3DArray)) {
position3DArray = new Float32Array(6 * totalLength * 3);
}
bucket.writeForMorph(position3DArray, positionIndex);
}
var bucketLength = bucket.lengthOfPositions;
positionIndex += 6 * bucketLength * 3;
colorIndex += bucketLength * 4;
texCoordExpandAndBatchIndexIndex += bucketLength * 4;
offset = bucket.updateIndices(totalIndices, vertexBufferOffset, vertexArrayBuckets, offset);
}
}
var positionBufferUsage = collection._positionBufferUsage.bufferUsage;
var texCoordExpandAndBatchIndexBufferUsage = BufferUsage.STATIC_DRAW;
collection._positionBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : positionArray,
usage : positionBufferUsage
});
var position3DBuffer;
if (defined(position3DArray)) {
position3DBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : position3DArray,
usage : positionBufferUsage
});
}
collection._texCoordExpandAndBatchIndexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : texCoordExpandAndBatchIndexArray,
usage : texCoordExpandAndBatchIndexBufferUsage
});
var positionSizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT;
var texCoordExpandAndBatchIndexSizeInBytes = 4 * Float32Array.BYTES_PER_ELEMENT;
var vbo = 0;
var numberOfIndicesArrays = totalIndices.length;
for ( var k = 0; k < numberOfIndicesArrays; ++k) {
indices = totalIndices[k];
if (indices.length > 0) {
var indicesArray = new Uint16Array(indices);
var indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : indicesArray,
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
vbo += vertexBufferOffset[k];
var positionHighOffset = 6 * (k * (positionSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * positionSizeInBytes);//componentsPerAttribute(3) * componentDatatype(4)
var positionLowOffset = positionSizeInBytes + positionHighOffset;
var prevPositionHighOffset = positionSizeInBytes + positionLowOffset;
var prevPositionLowOffset = positionSizeInBytes + prevPositionHighOffset;
var nextPositionHighOffset = positionSizeInBytes + prevPositionLowOffset;
var nextPositionLowOffset = positionSizeInBytes + nextPositionHighOffset;
var vertexTexCoordExpandAndBatchIndexBufferOffset = k * (texCoordExpandAndBatchIndexSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * texCoordExpandAndBatchIndexSizeInBytes;
var attributes = [{
index : attributeLocations.position3DHigh,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : positionHighOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.position3DLow,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : positionLowOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.position2DHigh,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : positionHighOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.position2DLow,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : positionLowOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.prevPosition3DHigh,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : prevPositionHighOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.prevPosition3DLow,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : prevPositionLowOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.prevPosition2DHigh,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : prevPositionHighOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.prevPosition2DLow,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : prevPositionLowOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.nextPosition3DHigh,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : nextPositionHighOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.nextPosition3DLow,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : nextPositionLowOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.nextPosition2DHigh,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : nextPositionHighOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.nextPosition2DLow,
componentsPerAttribute : 3,
componentDatatype : ComponentDatatype.FLOAT,
offsetInBytes : nextPositionLowOffset,
strideInBytes : 6 * positionSizeInBytes
}, {
index : attributeLocations.texCoordExpandAndBatchIndex,
componentsPerAttribute : 4,
componentDatatype : ComponentDatatype.FLOAT,
vertexBuffer : collection._texCoordExpandAndBatchIndexBuffer,
offsetInBytes : vertexTexCoordExpandAndBatchIndexBufferOffset
}];
var buffer3D;
var bufferProperty3D;
var buffer2D;
var bufferProperty2D;
if (mode === SceneMode.SCENE3D) {
buffer3D = collection._positionBuffer;
bufferProperty3D = 'vertexBuffer';
buffer2D = emptyVertexBuffer;
bufferProperty2D = 'value';
} else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
buffer3D = emptyVertexBuffer;
bufferProperty3D = 'value';
buffer2D = collection._positionBuffer;
bufferProperty2D = 'vertexBuffer';
} else {
buffer3D = position3DBuffer;
bufferProperty3D = 'vertexBuffer';
buffer2D = collection._positionBuffer;
bufferProperty2D = 'vertexBuffer';
}
attributes[0][bufferProperty3D] = buffer3D;
attributes[1][bufferProperty3D] = buffer3D;
attributes[2][bufferProperty2D] = buffer2D;
attributes[3][bufferProperty2D] = buffer2D;
attributes[4][bufferProperty3D] = buffer3D;
attributes[5][bufferProperty3D] = buffer3D;
attributes[6][bufferProperty2D] = buffer2D;
attributes[7][bufferProperty2D] = buffer2D;
attributes[8][bufferProperty3D] = buffer3D;
attributes[9][bufferProperty3D] = buffer3D;
attributes[10][bufferProperty2D] = buffer2D;
attributes[11][bufferProperty2D] = buffer2D;
var va = new VertexArray({
context : context,
attributes : attributes,
indexBuffer : indexBuffer
});
collection._vertexArrays.push({
va : va,
buckets : vertexArrayBuckets[k]
});
}
}
}
}
var scratchUniformArray = [];
function createMaterialId(material) {
var uniforms = Material._uniformList[material.type];
var length = uniforms.length;
scratchUniformArray.length = 2.0 * length;
var index = 0;
for (var i = 0; i < length; ++i) {
var uniform = uniforms[i];
scratchUniformArray[index] = uniform;
scratchUniformArray[index + 1] = material._uniforms[uniform]();
index += 2;
}
return material.type + ':' + JSON.stringify(scratchUniformArray);
}
function sortPolylinesIntoBuckets(collection) {
var mode = collection._mode;
var modelMatrix = collection._modelMatrix;
var polylineBuckets = collection._polylineBuckets = {};
var polylines = collection._polylines;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
var p = polylines[i];
if (p._actualPositions.length > 1) {
p.update();
var material = p.material;
var value = polylineBuckets[material.type];
if (!defined(value)) {
value = polylineBuckets[material.type] = new PolylineBucket(material, mode, modelMatrix);
}
value.addPolyline(p);
}
}
}
function updateMode(collection, frameState) {
var mode = frameState.mode;
if (collection._mode !== mode || (!Matrix4.equals(collection._modelMatrix, collection.modelMatrix))) {
collection._mode = mode;
collection._modelMatrix = Matrix4.clone(collection.modelMatrix);
collection._createVertexArray = true;
}
}
function removePolylines(collection) {
if (collection._polylinesRemoved) {
collection._polylinesRemoved = false;
var polylines = [];
var length = collection._polylines.length;
for ( var i = 0, j = 0; i < length; ++i) {
var polyline = collection._polylines[i];
if (defined(polyline)) {
polyline._index = j++;
polylines.push(polyline);
}
}
collection._polylines = polylines;
}
}
function releaseShaders(collection) {
var polylines = collection._polylines;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
if (defined(polylines[i])) {
var bucket = polylines[i]._bucket;
if (defined(bucket)) {
bucket.shaderProgram = bucket.shaderProgram && bucket.shaderProgram.destroy();
}
}
}
}
function destroyVertexArrays(collection) {
var length = collection._vertexArrays.length;
for ( var t = 0; t < length; ++t) {
collection._vertexArrays[t].va.destroy();
}
collection._vertexArrays.length = 0;
}
PolylineCollection.prototype._updatePolyline = function(polyline, propertyChanged) {
this._polylinesUpdated = true;
this._polylinesToUpdate.push(polyline);
++this._propertiesChanged[propertyChanged];
};
function destroyPolylines(collection) {
var polylines = collection._polylines;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
if (defined(polylines[i])) {
polylines[i]._destroy();
}
}
}
function VertexArrayBucketLocator(count, offset, bucket) {
this.count = count;
this.offset = offset;
this.bucket = bucket;
}
function PolylineBucket(material, mode, modelMatrix) {
this.polylines = [];
this.lengthOfPositions = 0;
this.material = material;
this.shaderProgram = undefined;
this.pickShaderProgram = undefined;
this.mode = mode;
this.modelMatrix = modelMatrix;
}
PolylineBucket.prototype.addPolyline = function(p) {
var polylines = this.polylines;
polylines.push(p);
p._actualLength = this.getPolylinePositionsLength(p);
this.lengthOfPositions += p._actualLength;
p._bucket = this;
};
PolylineBucket.prototype.updateShader = function(context, batchTable) {
if (defined(this.shaderProgram)) {
return;
}
var defines = ['DISTANCE_DISPLAY_CONDITION'];
var vsSource = batchTable.getVertexShaderCallback()(PolylineVS);
var vs = new ShaderSource({
defines : defines,
sources : [PolylineCommon, vsSource]
});
var fs = new ShaderSource({
sources : [this.material.shaderSource, PolylineFS]
});
var fsPick = new ShaderSource({
sources : fs.sources,
pickColorQualifier : 'varying'
});
this.shaderProgram = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
this.pickShaderProgram = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : fsPick,
attributeLocations : attributeLocations
});
};
function intersectsIDL(polyline) {
return Cartesian3.dot(Cartesian3.UNIT_X, polyline._boundingVolume.center) < 0 ||
polyline._boundingVolume.intersectPlane(Plane.ORIGIN_ZX_PLANE) === Intersect.INTERSECTING;
}
PolylineBucket.prototype.getPolylinePositionsLength = function(polyline) {
var length;
if (this.mode === SceneMode.SCENE3D || !intersectsIDL(polyline)) {
length = polyline._actualPositions.length;
return length * 4.0 - 4.0;
}
var count = 0;
var segmentLengths = polyline._segments.lengths;
length = segmentLengths.length;
for (var i = 0; i < length; ++i) {
count += segmentLengths[i] * 4.0 - 4.0;
}
return count;
};
var scratchWritePosition = new Cartesian3();
var scratchWritePrevPosition = new Cartesian3();
var scratchWriteNextPosition = new Cartesian3();
var scratchWriteVector = new Cartesian3();
var scratchPickColorCartesian = new Cartesian4();
var scratchWidthShowCartesian = new Cartesian2();
PolylineBucket.prototype.write = function(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection) {
var mode = this.mode;
var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI;
var polylines = this.polylines;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
var polyline = polylines[i];
var width = polyline.width;
var show = polyline.show && width > 0.0;
var polylineBatchIndex = polyline._index;
var segments = this.getSegments(polyline, projection);
var positions = segments.positions;
var lengths = segments.lengths;
var positionsLength = positions.length;
var pickColor = polyline.getPickId(context).color;
var segmentIndex = 0;
var count = 0;
var position;
for ( var j = 0; j < positionsLength; ++j) {
if (j === 0) {
if (polyline._loop) {
position = positions[positionsLength - 2];
} else {
position = scratchWriteVector;
Cartesian3.subtract(positions[0], positions[1], position);
Cartesian3.add(positions[0], position, position);
}
} else {
position = positions[j - 1];
}
Cartesian3.clone(position, scratchWritePrevPosition);
Cartesian3.clone(positions[j], scratchWritePosition);
if (j === positionsLength - 1) {
if (polyline._loop) {
position = positions[1];
} else {
position = scratchWriteVector;
Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], position);
Cartesian3.add(positions[positionsLength - 1], position, position);
}
} else {
position = positions[j + 1];
}
Cartesian3.clone(position, scratchWriteNextPosition);
var segmentLength = lengths[segmentIndex];
if (j === count + segmentLength) {
count += segmentLength;
++segmentIndex;
}
var segmentStart = j - count === 0;
var segmentEnd = j === count + lengths[segmentIndex] - 1;
if (mode === SceneMode.SCENE2D) {
scratchWritePrevPosition.z = 0.0;
scratchWritePosition.z = 0.0;
scratchWriteNextPosition.z = 0.0;
}
if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
if ((segmentStart || segmentEnd) && maxLon - Math.abs(scratchWritePosition.x) < 1.0) {
if ((scratchWritePosition.x < 0.0 && scratchWritePrevPosition.x > 0.0) ||
(scratchWritePosition.x > 0.0 && scratchWritePrevPosition.x < 0.0)) {
Cartesian3.clone(scratchWritePosition, scratchWritePrevPosition);
}
if ((scratchWritePosition.x < 0.0 && scratchWriteNextPosition.x > 0.0) ||
(scratchWritePosition.x > 0.0 && scratchWriteNextPosition.x < 0.0)) {
Cartesian3.clone(scratchWritePosition, scratchWriteNextPosition);
}
}
}
var startK = (segmentStart) ? 2 : 0;
var endK = (segmentEnd) ? 2 : 4;
for (var k = startK; k < endK; ++k) {
EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex);
EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6);
EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12);
var direction = (k - 2 < 0) ? -1.0 : 1.0;
texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex] = j / (positionsLength - 1); // s tex coord
texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 1] = 2 * (k % 2) - 1; // expand direction
texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 2] = direction;
texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 3] = polylineBatchIndex;
positionIndex += 6 * 3;
texCoordExpandAndBatchIndexIndex += 4;
}
}
var colorCartesian = scratchPickColorCartesian;
colorCartesian.x = Color.floatToByte(pickColor.red);
colorCartesian.y = Color.floatToByte(pickColor.green);
colorCartesian.z = Color.floatToByte(pickColor.blue);
colorCartesian.w = Color.floatToByte(pickColor.alpha);
var widthShowCartesian = scratchWidthShowCartesian;
widthShowCartesian.x = width;
widthShowCartesian.y = show ? 1.0 : 0.0;
var boundingSphere = mode === SceneMode.SCENE2D ? polyline._boundingVolume2D : polyline._boundingVolumeWC;
var encodedCenter = EncodedCartesian3.fromCartesian(boundingSphere.center, scratchUpdatePolylineEncodedCartesian);
var high = encodedCenter.high;
var low = Cartesian4.fromElements(encodedCenter.low.x, encodedCenter.low.y, encodedCenter.low.z, boundingSphere.radius, scratchUpdatePolylineCartesian4);
var nearFarCartesian = scratchNearFarCartesian2;
nearFarCartesian.x = 0.0;
nearFarCartesian.y = Number.MAX_VALUE;
var distanceDisplayCondition = polyline.distanceDisplayCondition;
if (defined(distanceDisplayCondition)) {
nearFarCartesian.x = distanceDisplayCondition.near;
nearFarCartesian.y = distanceDisplayCondition.far;
}
batchTable.setBatchedAttribute(polylineBatchIndex, 0, widthShowCartesian);
batchTable.setBatchedAttribute(polylineBatchIndex, 1, colorCartesian);
if (batchTable.attributes.length > 2) {
batchTable.setBatchedAttribute(polylineBatchIndex, 2, high);
batchTable.setBatchedAttribute(polylineBatchIndex, 3, low);
batchTable.setBatchedAttribute(polylineBatchIndex, 4, nearFarCartesian);
}
}
};
var morphPositionScratch = new Cartesian3();
var morphPrevPositionScratch = new Cartesian3();
var morphNextPositionScratch = new Cartesian3();
var morphVectorScratch = new Cartesian3();
PolylineBucket.prototype.writeForMorph = function(positionArray, positionIndex) {
var modelMatrix = this.modelMatrix;
var polylines = this.polylines;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
var polyline = polylines[i];
var positions = polyline._segments.positions;
var lengths = polyline._segments.lengths;
var positionsLength = positions.length;
var segmentIndex = 0;
var count = 0;
for ( var j = 0; j < positionsLength; ++j) {
var prevPosition;
if (j === 0) {
if (polyline._loop) {
prevPosition = positions[positionsLength - 2];
} else {
prevPosition = morphVectorScratch;
Cartesian3.subtract(positions[0], positions[1], prevPosition);
Cartesian3.add(positions[0], prevPosition, prevPosition);
}
} else {
prevPosition = positions[j - 1];
}
prevPosition = Matrix4.multiplyByPoint(modelMatrix, prevPosition, morphPrevPositionScratch);
var position = Matrix4.multiplyByPoint(modelMatrix, positions[j], morphPositionScratch);
var nextPosition;
if (j === positionsLength - 1) {
if (polyline._loop) {
nextPosition = positions[1];
} else {
nextPosition = morphVectorScratch;
Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], nextPosition);
Cartesian3.add(positions[positionsLength - 1], nextPosition, nextPosition);
}
} else {
nextPosition = positions[j + 1];
}
nextPosition = Matrix4.multiplyByPoint(modelMatrix, nextPosition, morphNextPositionScratch);
var segmentLength = lengths[segmentIndex];
if (j === count + segmentLength) {
count += segmentLength;
++segmentIndex;
}
var segmentStart = j - count === 0;
var segmentEnd = j === count + lengths[segmentIndex] - 1;
var startK = (segmentStart) ? 2 : 0;
var endK = (segmentEnd) ? 2 : 4;
for (var k = startK; k < endK; ++k) {
EncodedCartesian3.writeElements(position, positionArray, positionIndex);
EncodedCartesian3.writeElements(prevPosition, positionArray, positionIndex + 6);
EncodedCartesian3.writeElements(nextPosition, positionArray, positionIndex + 12);
positionIndex += 6 * 3;
}
}
}
};
var scratchSegmentLengths = new Array(1);
PolylineBucket.prototype.updateIndices = function(totalIndices, vertexBufferOffset, vertexArrayBuckets, offset) {
var vaCount = vertexArrayBuckets.length - 1;
var bucketLocator = new VertexArrayBucketLocator(0, offset, this);
vertexArrayBuckets[vaCount].push(bucketLocator);
var count = 0;
var indices = totalIndices[totalIndices.length - 1];
var indicesCount = 0;
if (indices.length > 0) {
indicesCount = indices[indices.length - 1] + 1;
}
var polylines = this.polylines;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
var polyline = polylines[i];
polyline._locatorBuckets = [];
var segments;
if (this.mode === SceneMode.SCENE3D) {
segments = scratchSegmentLengths;
var positionsLength = polyline._actualPositions.length;
if (positionsLength > 0) {
segments[0] = positionsLength;
} else {
continue;
}
} else {
segments = polyline._segments.lengths;
}
var numberOfSegments = segments.length;
if (numberOfSegments > 0) {
var segmentIndexCount = 0;
for ( var j = 0; j < numberOfSegments; ++j) {
var segmentLength = segments[j] - 1.0;
for ( var k = 0; k < segmentLength; ++k) {
if (indicesCount + 4 >= CesiumMath.SIXTY_FOUR_KILOBYTES - 2) {
polyline._locatorBuckets.push({
locator : bucketLocator,
count : segmentIndexCount
});
segmentIndexCount = 0;
vertexBufferOffset.push(4);
indices = [];
totalIndices.push(indices);
indicesCount = 0;
bucketLocator.count = count;
count = 0;
offset = 0;
bucketLocator = new VertexArrayBucketLocator(0, 0, this);
vertexArrayBuckets[++vaCount] = [bucketLocator];
}
indices.push(indicesCount, indicesCount + 2, indicesCount + 1);
indices.push(indicesCount + 1, indicesCount + 2, indicesCount + 3);
segmentIndexCount += 6;
count += 6;
offset += 6;
indicesCount += 4;
}
}
polyline._locatorBuckets.push({
locator : bucketLocator,
count : segmentIndexCount
});
if (indicesCount + 4 >= CesiumMath.SIXTY_FOUR_KILOBYTES - 2) {
vertexBufferOffset.push(0);
indices = [];
totalIndices.push(indices);
indicesCount = 0;
bucketLocator.count = count;
offset = 0;
count = 0;
bucketLocator = new VertexArrayBucketLocator(0, 0, this);
vertexArrayBuckets[++vaCount] = [bucketLocator];
}
}
polyline._clean();
}
bucketLocator.count = count;
return offset;
};
PolylineBucket.prototype.getPolylineStartIndex = function(polyline) {
var polylines = this.polylines;
var positionIndex = 0;
var length = polylines.length;
for ( var i = 0; i < length; ++i) {
var p = polylines[i];
if (p === polyline) {
break;
}
positionIndex += p._actualLength;
}
return positionIndex;
};
var scratchSegments = {
positions : undefined,
lengths : undefined
};
var scratchLengths = new Array(1);
var pscratch = new Cartesian3();
var scratchCartographic = new Cartographic();
PolylineBucket.prototype.getSegments = function(polyline, projection) {
var positions = polyline._actualPositions;
if (this.mode === SceneMode.SCENE3D) {
scratchLengths[0] = positions.length;
scratchSegments.positions = positions;
scratchSegments.lengths = scratchLengths;
return scratchSegments;
}
if (intersectsIDL(polyline)) {
positions = polyline._segments.positions;
}
var ellipsoid = projection.ellipsoid;
var newPositions = [];
var modelMatrix = this.modelMatrix;
var length = positions.length;
var position;
var p = pscratch;
for ( var n = 0; n < length; ++n) {
position = positions[n];
p = Matrix4.multiplyByPoint(modelMatrix, position, p);
newPositions.push(projection.project(ellipsoid.cartesianToCartographic(p, scratchCartographic)));
}
if (newPositions.length > 0) {
polyline._boundingVolume2D = BoundingSphere.fromPoints(newPositions, polyline._boundingVolume2D);
var center2D = polyline._boundingVolume2D.center;
polyline._boundingVolume2D.center = new Cartesian3(center2D.z, center2D.x, center2D.y);
}
scratchSegments.positions = newPositions;
scratchSegments.lengths = polyline._segments.lengths;
return scratchSegments;
};
var scratchPositionsArray;
PolylineBucket.prototype.writeUpdate = function(index, polyline, positionBuffer, projection) {
var mode = this.mode;
var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI;
var positionsLength = polyline._actualLength;
if (positionsLength) {
index += this.getPolylineStartIndex(polyline);
var positionArray = scratchPositionsArray;
var positionsArrayLength = 6 * positionsLength * 3;
if (!defined(positionArray) || positionArray.length < positionsArrayLength) {
positionArray = scratchPositionsArray = new Float32Array(positionsArrayLength);
} else if (positionArray.length > positionsArrayLength) {
positionArray = new Float32Array(positionArray.buffer, 0, positionsArrayLength);
}
var segments = this.getSegments(polyline, projection);
var positions = segments.positions;
var lengths = segments.lengths;
var positionIndex = 0;
var segmentIndex = 0;
var count = 0;
var position;
positionsLength = positions.length;
for ( var i = 0; i < positionsLength; ++i) {
if (i === 0) {
if (polyline._loop) {
position = positions[positionsLength - 2];
} else {
position = scratchWriteVector;
Cartesian3.subtract(positions[0], positions[1], position);
Cartesian3.add(positions[0], position, position);
}
} else {
position = positions[i - 1];
}
Cartesian3.clone(position, scratchWritePrevPosition);
Cartesian3.clone(positions[i], scratchWritePosition);
if (i === positionsLength - 1) {
if (polyline._loop) {
position = positions[1];
} else {
position = scratchWriteVector;
Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], position);
Cartesian3.add(positions[positionsLength - 1], position, position);
}
} else {
position = positions[i + 1];
}
Cartesian3.clone(position, scratchWriteNextPosition);
var segmentLength = lengths[segmentIndex];
if (i === count + segmentLength) {
count += segmentLength;
++segmentIndex;
}
var segmentStart = i - count === 0;
var segmentEnd = i === count + lengths[segmentIndex] - 1;
if (mode === SceneMode.SCENE2D) {
scratchWritePrevPosition.z = 0.0;
scratchWritePosition.z = 0.0;
scratchWriteNextPosition.z = 0.0;
}
if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
if ((segmentStart || segmentEnd) && maxLon - Math.abs(scratchWritePosition.x) < 1.0) {
if ((scratchWritePosition.x < 0.0 && scratchWritePrevPosition.x > 0.0) ||
(scratchWritePosition.x > 0.0 && scratchWritePrevPosition.x < 0.0)) {
Cartesian3.clone(scratchWritePosition, scratchWritePrevPosition);
}
if ((scratchWritePosition.x < 0.0 && scratchWriteNextPosition.x > 0.0) ||
(scratchWritePosition.x > 0.0 && scratchWriteNextPosition.x < 0.0)) {
Cartesian3.clone(scratchWritePosition, scratchWriteNextPosition);
}
}
}
var startJ = (segmentStart) ? 2 : 0;
var endJ = (segmentEnd) ? 2 : 4;
for (var j = startJ; j < endJ; ++j) {
EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex);
EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6);
EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12);
positionIndex += 6 * 3;
}
}
positionBuffer.copyFromArrayView(positionArray, 6 * 3 * Float32Array.BYTES_PER_ELEMENT * index);
}
};
return PolylineCollection;
});
/*global define*/
define('DataSources/ScaledPositionProperty',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/Event',
'../Core/ReferenceFrame',
'./Property'
], function(
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Event,
ReferenceFrame,
Property) {
'use strict';
/**
* This is a temporary class for scaling position properties to the WGS84 surface.
* It will go away or be refactored to support data with arbitrary height references.
* @private
*/
function ScaledPositionProperty(value) {
this._definitionChanged = new Event();
this._value = undefined;
this._removeSubscription = undefined;
this.setValue(value);
}
defineProperties(ScaledPositionProperty.prototype, {
isConstant : {
get : function() {
return Property.isConstant(this._value);
}
},
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
referenceFrame : {
get : function() {
return defined(this._value) ? this._value.referenceFrame : ReferenceFrame.FIXED;
}
}
});
ScaledPositionProperty.prototype.getValue = function(time, result) {
return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
};
ScaledPositionProperty.prototype.setValue = function(value) {
if (this._value !== value) {
this._value = value;
if (defined(this._removeSubscription)) {
this._removeSubscription();
this._removeSubscription = undefined;
}
if (defined(value)) {
this._removeSubscription = value.definitionChanged.addEventListener(this._raiseDefinitionChanged, this);
}
this._definitionChanged.raiseEvent(this);
}
};
ScaledPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(referenceFrame)) {
throw new DeveloperError('referenceFrame is required.');
}
if (!defined(this._value)) {
return undefined;
}
result = this._value.getValueInReferenceFrame(time, referenceFrame, result);
return defined(result) ? Ellipsoid.WGS84.scaleToGeodeticSurface(result, result) : undefined;
};
ScaledPositionProperty.prototype.equals = function(other) {
return this === other || (other instanceof ScaledPositionProperty && this._value === other._value);
};
ScaledPositionProperty.prototype._raiseDefinitionChanged = function() {
this._definitionChanged.raiseEvent(this);
};
return ScaledPositionProperty;
});
/*global define*/
define('DataSources/PathVisualizer',[
'../Core/AssociativeArray',
'../Core/Cartesian3',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/JulianDate',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/ReferenceFrame',
'../Core/TimeInterval',
'../Core/Transforms',
'../Scene/PolylineCollection',
'../Scene/SceneMode',
'./CompositePositionProperty',
'./ConstantPositionProperty',
'./MaterialProperty',
'./Property',
'./ReferenceProperty',
'./SampledPositionProperty',
'./ScaledPositionProperty',
'./TimeIntervalCollectionPositionProperty'
], function(
AssociativeArray,
Cartesian3,
defined,
destroyObject,
DeveloperError,
JulianDate,
Matrix3,
Matrix4,
ReferenceFrame,
TimeInterval,
Transforms,
PolylineCollection,
SceneMode,
CompositePositionProperty,
ConstantPositionProperty,
MaterialProperty,
Property,
ReferenceProperty,
SampledPositionProperty,
ScaledPositionProperty,
TimeIntervalCollectionPositionProperty) {
'use strict';
var defaultResolution = 60.0;
var defaultWidth = 1.0;
var scratchTimeInterval = new TimeInterval();
var subSampleCompositePropertyScratch = new TimeInterval();
var subSampleIntervalPropertyScratch = new TimeInterval();
function EntityData(entity) {
this.entity = entity;
this.polyline = undefined;
this.index = undefined;
this.updater = undefined;
}
function subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, startingIndex, result) {
var r = startingIndex;
//Always step exactly on start (but only use it if it exists.)
var tmp;
tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
if (defined(tmp)) {
result[r++] = tmp;
}
var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
//Iterate over all interval times and add the ones that fall in our
//time range. Note that times can contain data outside of
//the intervals range. This is by design for use with interpolation.
var t = 0;
var len = times.length;
var current = times[t];
var loopStop = stop;
var sampling = false;
var sampleStepsToTake;
var sampleStepsTaken;
var sampleStepSize;
while (t < len) {
if (!steppedOnNow && JulianDate.greaterThanOrEquals(current, updateTime)) {
tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[r]);
if (defined(tmp)) {
result[r++] = tmp;
}
steppedOnNow = true;
}
if (JulianDate.greaterThan(current, start) && JulianDate.lessThan(current, loopStop) && !current.equals(updateTime)) {
tmp = property.getValueInReferenceFrame(current, referenceFrame, result[r]);
if (defined(tmp)) {
result[r++] = tmp;
}
}
if (t < (len - 1)) {
if (maximumStep > 0 && !sampling) {
var next = times[t + 1];
var secondsUntilNext = JulianDate.secondsDifference(next, current);
sampling = secondsUntilNext > maximumStep;
if (sampling) {
sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep);
sampleStepsTaken = 0;
sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2);
sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1);
}
}
if (sampling && sampleStepsTaken < sampleStepsToTake) {
current = JulianDate.addSeconds(current, sampleStepSize, new JulianDate());
sampleStepsTaken++;
continue;
}
}
sampling = false;
t++;
current = times[t];
}
//Always step exactly on stop (but only use it if it exists.)
tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[r]);
if (defined(tmp)) {
result[r++] = tmp;
}
return r;
}
function subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
var tmp;
var i = 0;
var index = startingIndex;
var time = start;
var stepSize = Math.max(maximumStep, 60);
var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
while (JulianDate.lessThan(time, stop)) {
if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
steppedOnNow = true;
tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[index]);
if (defined(tmp)) {
result[index] = tmp;
index++;
}
}
tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
if (defined(tmp)) {
result[index] = tmp;
index++;
}
i++;
time = JulianDate.addSeconds(start, stepSize * i, new JulianDate());
}
//Always sample stop.
tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
if (defined(tmp)) {
result[index] = tmp;
index++;
}
return index;
}
function subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
subSampleIntervalPropertyScratch.start = start;
subSampleIntervalPropertyScratch.stop = stop;
var index = startingIndex;
var intervals = property.intervals;
for (var i = 0; i < intervals.length; i++) {
var interval = intervals.get(i);
if (!TimeInterval.intersect(interval, subSampleIntervalPropertyScratch, scratchTimeInterval).isEmpty) {
var time = interval.start;
if (!interval.isStartIncluded) {
if (interval.isStopIncluded) {
time = interval.stop;
} else {
time = JulianDate.addSeconds(interval.start, JulianDate.secondsDifference(interval.stop, interval.start) / 2, new JulianDate());
}
}
var tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
if (defined(tmp)) {
result[index] = tmp;
index++;
}
}
}
return index;
}
function subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
var tmp = property.getValueInReferenceFrame(start, referenceFrame, result[startingIndex]);
if (defined(tmp)) {
result[startingIndex++] = tmp;
}
return startingIndex;
}
function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
subSampleCompositePropertyScratch.start = start;
subSampleCompositePropertyScratch.stop = stop;
var index = startingIndex;
var intervals = property.intervals;
for (var i = 0; i < intervals.length; i++) {
var interval = intervals.get(i);
if (!TimeInterval.intersect(interval, subSampleCompositePropertyScratch, scratchTimeInterval).isEmpty) {
var intervalStart = interval.start;
var intervalStop = interval.stop;
var sampleStart = start;
if (JulianDate.greaterThan(intervalStart, sampleStart)) {
sampleStart = intervalStart;
}
var sampleStop = stop;
if (JulianDate.lessThan(intervalStop, sampleStop)) {
sampleStop = intervalStop;
}
index = reallySubSample(interval.data, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
}
}
return index;
}
function reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, index, result) {
var innerProperty = property;
while (innerProperty instanceof ReferenceProperty || innerProperty instanceof ScaledPositionProperty) {
if (innerProperty instanceof ReferenceProperty) {
innerProperty = innerProperty.resolvedProperty;
}
if (innerProperty instanceof ScaledPositionProperty) {
innerProperty = innerProperty._value;
}
}
if (innerProperty instanceof SampledPositionProperty) {
var times = innerProperty._property._times;
index = subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, index, result);
} else if (innerProperty instanceof CompositePositionProperty) {
index = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
} else if (innerProperty instanceof TimeIntervalCollectionPositionProperty) {
index = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
} else if (innerProperty instanceof ConstantPositionProperty) {
index = subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
} else {
//Fallback to generic sampling.
index = subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
}
return index;
}
function subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result) {
if (!defined(result)) {
result = [];
}
var length = reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
result.length = length;
return result;
}
var toFixedScratch = new Matrix3();
function PolylineUpdater(scene, referenceFrame) {
this._unusedIndexes = [];
this._polylineCollection = new PolylineCollection();
this._scene = scene;
this._referenceFrame = referenceFrame;
scene.primitives.add(this._polylineCollection);
}
PolylineUpdater.prototype.update = function(time) {
if (this._referenceFrame === ReferenceFrame.INERTIAL) {
var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch);
if (!defined(toFixed)) {
toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch);
}
Matrix4.fromRotationTranslation(toFixed, Cartesian3.ZERO, this._polylineCollection.modelMatrix);
}
};
PolylineUpdater.prototype.updateObject = function(time, item) {
var entity = item.entity;
var pathGraphics = entity._path;
var positionProperty = entity._position;
var sampleStart;
var sampleStop;
var showProperty = pathGraphics._show;
var polyline = item.polyline;
var show = entity.isShowing && (!defined(showProperty) || showProperty.getValue(time));
//While we want to show the path, there may not actually be anything to show
//depending on lead/trail settings. Compute the interval of the path to
//show and check against actual availability.
if (show) {
var leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
var trailTime = Property.getValueOrUndefined(pathGraphics._trailTime, time);
var availability = entity._availability;
var hasAvailability = defined(availability);
var hasLeadTime = defined(leadTime);
var hasTrailTime = defined(trailTime);
//Objects need to have either defined availability or both a lead and trail time in order to
//draw a path (since we can't draw "infinite" paths.
show = hasAvailability || (hasLeadTime && hasTrailTime);
//The final step is to compute the actual start/stop times of the path to show.
//If current time is outside of the availability interval, there's a chance that
//we won't have to draw anything anyway.
if (show) {
if (hasTrailTime) {
sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
}
if (hasLeadTime) {
sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
}
if (hasAvailability) {
var start = availability.start;
var stop = availability.stop;
if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
sampleStart = start;
}
if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
sampleStop = stop;
}
}
show = JulianDate.lessThan(sampleStart, sampleStop);
}
}
if (!show) {
//don't bother creating or updating anything else
if (defined(polyline)) {
this._unusedIndexes.push(item.index);
item.polyline = undefined;
polyline.show = false;
item.index = undefined;
}
return;
}
if (!defined(polyline)) {
var unusedIndexes = this._unusedIndexes;
var length = unusedIndexes.length;
if (length > 0) {
var index = unusedIndexes.pop();
polyline = this._polylineCollection.get(index);
item.index = index;
} else {
item.index = this._polylineCollection.length;
polyline = this._polylineCollection.add();
}
polyline.id = entity;
item.polyline = polyline;
}
var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution);
polyline.show = true;
polyline.positions = subSample(positionProperty, sampleStart, sampleStop, time, this._referenceFrame, resolution, polyline.positions.slice());
polyline.material = MaterialProperty.getValue(time, pathGraphics._material, polyline.material);
polyline.width = Property.getValueOrDefault(pathGraphics._width, time, defaultWidth);
polyline.distanceDisplayCondition = Property.getValueOrUndefined(pathGraphics._distanceDisplayCondition, time, polyline.distanceDisplayCondition);
};
PolylineUpdater.prototype.removeObject = function(item) {
var polyline = item.polyline;
if (defined(polyline)) {
this._unusedIndexes.push(item.index);
item.polyline = undefined;
polyline.show = false;
polyline.id = undefined;
item.index = undefined;
}
};
PolylineUpdater.prototype.destroy = function() {
this._scene.primitives.remove(this._polylineCollection);
return destroyObject(this);
};
/**
* A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
* @alias PathVisualizer
* @constructor
*
* @param {Scene} scene The scene the primitives will be rendered in.
* @param {EntityCollection} entityCollection The entityCollection to visualize.
*/
function PathVisualizer(scene, entityCollection) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(entityCollection)) {
throw new DeveloperError('entityCollection is required.');
}
entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this);
this._scene = scene;
this._updaters = {};
this._entityCollection = entityCollection;
this._items = new AssociativeArray();
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
}
/**
* Updates all of the primitives created by this visualizer to match their
* Entity counterpart at the given time.
*
* @param {JulianDate} time The time to update to.
* @returns {Boolean} This function always returns true.
*/
PathVisualizer.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var updaters = this._updaters;
for ( var key in updaters) {
if (updaters.hasOwnProperty(key)) {
updaters[key].update(time);
}
}
var items = this._items.values;
for (var i = 0, len = items.length; i < len; i++) {
var item = items[i];
var entity = item.entity;
var positionProperty = entity._position;
var lastUpdater = item.updater;
var frameToVisualize = ReferenceFrame.FIXED;
if (this._scene.mode === SceneMode.SCENE3D) {
frameToVisualize = positionProperty.referenceFrame;
}
var currentUpdater = this._updaters[frameToVisualize];
if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) {
currentUpdater.updateObject(time, item);
continue;
}
if (defined(lastUpdater)) {
lastUpdater.removeObject(item);
}
if (!defined(currentUpdater)) {
currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
currentUpdater.update(time);
this._updaters[frameToVisualize] = currentUpdater;
}
item.updater = currentUpdater;
if (defined(currentUpdater)) {
currentUpdater.updateObject(time, item);
}
}
return true;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
PathVisualizer.prototype.isDestroyed = function() {
return false;
};
/**
* Removes and destroys all primitives created by this instance.
*/
PathVisualizer.prototype.destroy = function() {
this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this);
var updaters = this._updaters;
for ( var key in updaters) {
if (updaters.hasOwnProperty(key)) {
updaters[key].destroy();
}
}
return destroyObject(this);
};
PathVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
var i;
var entity;
var item;
var items = this._items;
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
if (defined(entity._path) && defined(entity._position)) {
items.set(entity.id, new EntityData(entity));
}
}
for (i = changed.length - 1; i > -1; i--) {
entity = changed[i];
if (defined(entity._path) && defined(entity._position)) {
if (!items.contains(entity.id)) {
items.set(entity.id, new EntityData(entity));
}
} else {
item = items.get(entity.id);
if (defined(item)) {
item.updater.removeObject(item);
items.remove(entity.id);
}
}
}
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
item = items.get(entity.id);
if (defined(item)) {
if (defined(item.updater)) {
item.updater.removeObject(item);
}
items.remove(entity.id);
}
}
};
//for testing
PathVisualizer._subSample = subSample;
return PathVisualizer;
});
/*global define*/
define('DataSources/PointVisualizer',[
'../Core/AssociativeArray',
'../Core/Cartesian3',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/NearFarScalar',
'../Scene/HeightReference',
'./BoundingSphereState',
'./Property'
], function(
AssociativeArray,
Cartesian3,
Color,
defined,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
NearFarScalar,
HeightReference,
BoundingSphereState,
Property) {
'use strict';
var defaultColor = Color.WHITE;
var defaultOutlineColor = Color.BLACK;
var defaultOutlineWidth = 0.0;
var defaultPixelSize = 1.0;
var color = new Color();
var position = new Cartesian3();
var outlineColor = new Color();
var scaleByDistance = new NearFarScalar();
var translucencyByDistance = new NearFarScalar();
var distanceDisplayCondition = new DistanceDisplayCondition();
function EntityData(entity) {
this.entity = entity;
this.pointPrimitive = undefined;
this.billboard = undefined;
this.color = undefined;
this.outlineColor = undefined;
this.pixelSize = undefined;
this.outlineWidth = undefined;
}
/**
* A {@link Visualizer} which maps {@link Entity#point} to a {@link PointPrimitive}.
* @alias PointVisualizer
* @constructor
*
* @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
* @param {EntityCollection} entityCollection The entityCollection to visualize.
*/
function PointVisualizer(entityCluster, entityCollection) {
if (!defined(entityCluster)) {
throw new DeveloperError('entityCluster is required.');
}
if (!defined(entityCollection)) {
throw new DeveloperError('entityCollection is required.');
}
entityCollection.collectionChanged.addEventListener(PointVisualizer.prototype._onCollectionChanged, this);
this._cluster = entityCluster;
this._entityCollection = entityCollection;
this._items = new AssociativeArray();
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
}
/**
* Updates the primitives created by this visualizer to match their
* Entity counterpart at the given time.
*
* @param {JulianDate} time The time to update to.
* @returns {Boolean} This function always returns true.
*/
PointVisualizer.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var items = this._items.values;
var cluster = this._cluster;
for (var i = 0, len = items.length; i < len; i++) {
var item = items[i];
var entity = item.entity;
var pointGraphics = entity._point;
var pointPrimitive = item.pointPrimitive;
var billboard = item.billboard;
var heightReference = Property.getValueOrDefault(pointGraphics._heightReference, time, HeightReference.NONE);
var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(pointGraphics._show, time, true);
if (show) {
position = Property.getValueOrUndefined(entity._position, time, position);
show = defined(position);
}
if (!show) {
returnPrimitive(item, entity, cluster);
continue;
}
if (!Property.isConstant(entity._position)) {
cluster._clusterDirty = true;
}
var needsRedraw = false;
if ((heightReference !== HeightReference.NONE) && !defined(billboard)) {
if (defined(pointPrimitive)) {
returnPrimitive(item, entity, cluster);
pointPrimitive = undefined;
}
billboard = cluster.getBillboard(entity);
billboard.id = entity;
billboard.image = undefined;
item.billboard = billboard;
needsRedraw = true;
} else if ((heightReference === HeightReference.NONE) && !defined(pointPrimitive)) {
if (defined(billboard)) {
returnPrimitive(item, entity, cluster);
billboard = undefined;
}
pointPrimitive = cluster.getPoint(entity);
pointPrimitive.id = entity;
item.pointPrimitive = pointPrimitive;
}
if (defined(pointPrimitive)) {
pointPrimitive.show = true;
pointPrimitive.position = position;
pointPrimitive.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance);
pointPrimitive.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance);
pointPrimitive.color = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color);
pointPrimitive.outlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
pointPrimitive.outlineWidth = Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth);
pointPrimitive.pixelSize = Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize);
pointPrimitive.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
} else { // billboard
billboard.show = true;
billboard.position = position;
billboard.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance);
billboard.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance);
billboard.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
billboard.heightReference = heightReference;
var newColor = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color);
var newOutlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
var newOutlineWidth = Math.round(Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth));
var newPixelSize = Math.max(1, Math.round(Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize)));
if (newOutlineWidth > 0) {
billboard.scale = 1.0;
needsRedraw = needsRedraw || //
newOutlineWidth !== item.outlineWidth || //
newPixelSize !== item.pixelSize || //
!Color.equals(newColor, item.color) || //
!Color.equals(newOutlineColor, item.outlineColor);
} else {
billboard.scale = newPixelSize / 50.0;
newPixelSize = 50.0;
needsRedraw = needsRedraw || //
newOutlineWidth !== item.outlineWidth || //
!Color.equals(newColor, item.color) || //
!Color.equals(newOutlineColor, item.outlineColor);
}
if (needsRedraw) {
item.color = Color.clone(newColor, item.color);
item.outlineColor = Color.clone(newOutlineColor, item.outlineColor);
item.pixelSize = newPixelSize;
item.outlineWidth = newOutlineWidth;
var centerAlpha = newColor.alpha;
var cssColor = newColor.toCssColorString();
var cssOutlineColor = newOutlineColor.toCssColorString();
var textureId = JSON.stringify([cssColor, newPixelSize, cssOutlineColor, newOutlineWidth]);
billboard.setImage(textureId, createCallback(centerAlpha, cssColor, cssOutlineColor, newOutlineWidth, newPixelSize));
}
}
}
return true;
};
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
PointVisualizer.prototype.getBoundingSphere = function(entity, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var item = this._items.get(entity.id);
if (!defined(item) || !(defined(item.pointPrimitive) || defined(item.billboard))) {
return BoundingSphereState.FAILED;
}
if (defined(item.pointPrimitive)) {
result.center = Cartesian3.clone(item.pointPrimitive.position, result.center);
} else {
var billboard = item.billboard;
if (!defined(billboard._clampedPosition)) {
return BoundingSphereState.PENDING;
}
result.center = Cartesian3.clone(billboard._clampedPosition, result.center);
}
result.radius = 0;
return BoundingSphereState.DONE;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
PointVisualizer.prototype.isDestroyed = function() {
return false;
};
/**
* Removes and destroys all primitives created by this instance.
*/
PointVisualizer.prototype.destroy = function() {
this._entityCollection.collectionChanged.removeEventListener(PointVisualizer.prototype._onCollectionChanged, this);
var entities = this._entityCollection.values;
for (var i = 0; i < entities.length; i++) {
this._cluster.removePoint(entities[i]);
}
return destroyObject(this);
};
PointVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
var i;
var entity;
var items = this._items;
var cluster = this._cluster;
for (i = added.length - 1; i > -1; i--) {
entity = added[i];
if (defined(entity._point) && defined(entity._position)) {
items.set(entity.id, new EntityData(entity));
}
}
for (i = changed.length - 1; i > -1; i--) {
entity = changed[i];
if (defined(entity._point) && defined(entity._position)) {
if (!items.contains(entity.id)) {
items.set(entity.id, new EntityData(entity));
}
} else {
returnPrimitive(items.get(entity.id), entity, cluster);
items.remove(entity.id);
}
}
for (i = removed.length - 1; i > -1; i--) {
entity = removed[i];
returnPrimitive(items.get(entity.id), entity, cluster);
items.remove(entity.id);
}
};
function returnPrimitive(item, entity, cluster) {
if (defined(item)) {
var pointPrimitive = item.pointPrimitive;
if (defined(pointPrimitive)) {
item.pointPrimitive = undefined;
cluster.removePoint(entity);
return;
}
var billboard = item.billboard;
if (defined(billboard)) {
item.billboard = undefined;
cluster.removeBillboard(entity);
}
}
}
function createCallback(centerAlpha, cssColor, cssOutlineColor, cssOutlineWidth, newPixelSize) {
return function(id) {
var canvas = document.createElement('canvas');
var length = newPixelSize + (2 * cssOutlineWidth);
canvas.height = canvas.width = length;
var context2D = canvas.getContext('2d');
context2D.clearRect(0, 0, length, length);
if (cssOutlineWidth !== 0) {
context2D.beginPath();
context2D.arc(length / 2, length / 2, length / 2, 0, 2 * Math.PI, true);
context2D.closePath();
context2D.fillStyle = cssOutlineColor;
context2D.fill();
// Punch a hole in the center if needed.
if (centerAlpha < 1.0) {
context2D.save();
context2D.globalCompositeOperation = 'destination-out';
context2D.beginPath();
context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true);
context2D.closePath();
context2D.fillStyle = 'black';
context2D.fill();
context2D.restore();
}
}
context2D.beginPath();
context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true);
context2D.closePath();
context2D.fillStyle = cssColor;
context2D.fill();
return canvas;
};
}
return PointVisualizer;
});
/*global define*/
define('DataSources/PolygonGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/isArray',
'../Core/Iso8601',
'../Core/oneTimeWarning',
'../Core/PolygonGeometry',
'../Core/PolygonHierarchy',
'../Core/PolygonOutlineGeometry',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/GroundPrimitive',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
isArray,
Iso8601,
oneTimeWarning,
PolygonGeometry,
PolygonHierarchy,
PolygonOutlineGeometry,
ShowGeometryInstanceAttribute,
GroundPrimitive,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.polygonHierarchy = undefined;
this.perPositionHeight = undefined;
this.closeTop = undefined;
this.closeBottom = undefined;
this.height = undefined;
this.extrudedHeight = undefined;
this.granularity = undefined;
this.stRotation = undefined;
}
/**
* A {@link GeometryUpdater} for polygons.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias PolygonGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function PolygonGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(PolygonGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._isClosed = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._onTerrain = false;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'polygon', entity.polygon, undefined);
}
defineProperties(PolygonGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof PolygonGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof PolygonGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(PolygonGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
get : function() {
return this._isClosed;
}
},
/**
* Gets a value indicating if the geometry should be drawn on terrain.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
onTerrain : {
get : function() {
return this._onTerrain;
}
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof PolygonGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
PolygonGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
PolygonGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
PolygonGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new PolygonGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
PolygonGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new PolygonOutlineGeometry(this._options),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
PolygonGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
PolygonGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
PolygonGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'polygon')) {
return;
}
var polygon = this._entity.polygon;
if (!defined(polygon)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = polygon.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var perPositionHeightProperty = polygon.perPositionHeight;
var perPositionHeightEnabled = defined(perPositionHeightProperty) && (perPositionHeightProperty.isConstant ? perPositionHeightProperty.getValue(Iso8601.MINIMUM_VALUE) : true);
var outlineProperty = polygon.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var hierarchy = polygon.hierarchy;
var show = polygon.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(hierarchy))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(polygon.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(polygon.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(polygon.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(polygon.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(polygon.distanceDisplayCondition, defaultDistanceDisplayCondition);
var height = polygon.height;
var extrudedHeight = polygon.extrudedHeight;
var granularity = polygon.granularity;
var stRotation = polygon.stRotation;
var outlineWidth = polygon.outlineWidth;
var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) && isColorMaterial &&
!perPositionHeightEnabled && GroundPrimitive.isSupported(this._scene);
if (outlineEnabled && onTerrain) {
oneTimeWarning(oneTimeWarning.geometryOutlines);
outlineEnabled = false;
}
var perPositionHeight = polygon.perPositionHeight;
var closeTop = polygon.closeTop;
var closeBottom = polygon.closeBottom;
this._fillEnabled = fillEnabled;
this._onTerrain = onTerrain;
this._outlineEnabled = outlineEnabled;
if (!hierarchy.isConstant || //
!Property.isConstant(height) || //
!Property.isConstant(extrudedHeight) || //
!Property.isConstant(granularity) || //
!Property.isConstant(stRotation) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(perPositionHeightProperty) || //
!Property.isConstant(perPositionHeight) || //
!Property.isConstant(closeTop) || //
!Property.isConstant(closeBottom) || //
(onTerrain && !Property.isConstant(material))) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
var hierarchyValue = hierarchy.getValue(Iso8601.MINIMUM_VALUE);
if (isArray(hierarchyValue)) {
hierarchyValue = new PolygonHierarchy(hierarchyValue);
}
var heightValue = Property.getValueOrUndefined(height, Iso8601.MINIMUM_VALUE);
var closeTopValue = Property.getValueOrDefault(closeTop, Iso8601.MINIMUM_VALUE, true);
var closeBottomValue = Property.getValueOrDefault(closeBottom, Iso8601.MINIMUM_VALUE, true);
var extrudedHeightValue = Property.getValueOrUndefined(extrudedHeight, Iso8601.MINIMUM_VALUE);
options.polygonHierarchy = hierarchyValue;
options.height = heightValue;
options.extrudedHeight = extrudedHeightValue;
options.granularity = Property.getValueOrUndefined(granularity, Iso8601.MINIMUM_VALUE);
options.stRotation = Property.getValueOrUndefined(stRotation, Iso8601.MINIMUM_VALUE);
options.perPositionHeight = Property.getValueOrUndefined(perPositionHeight, Iso8601.MINIMUM_VALUE);
options.closeTop = closeTopValue;
options.closeBottom = closeBottomValue;
this._outlineWidth = Property.getValueOrDefault(outlineWidth, Iso8601.MINIMUM_VALUE, 1.0);
this._isClosed = defined(extrudedHeightValue) && extrudedHeightValue !== heightValue && closeTopValue && closeBottomValue;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
PolygonGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
this._primitives = primitives;
this._groundPrimitives = groundPrimitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var geometryUpdater = this._geometryUpdater;
var onTerrain = geometryUpdater._onTerrain;
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._outlinePrimitive = undefined;
}
this._primitive = undefined;
var entity = geometryUpdater._entity;
var polygon = entity.polygon;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polygon.show, time, true)) {
return;
}
var options = this._options;
var hierarchy = Property.getValueOrUndefined(polygon.hierarchy, time);
if (!defined(hierarchy)) {
return;
}
if (isArray(hierarchy)) {
options.polygonHierarchy = new PolygonHierarchy(hierarchy);
} else {
options.polygonHierarchy = hierarchy;
}
var closeTopValue = Property.getValueOrDefault(polygon.closeTop, time, true);
var closeBottomValue = Property.getValueOrDefault(polygon.closeBottom, time, true);
options.height = Property.getValueOrUndefined(polygon.height, time);
options.extrudedHeight = Property.getValueOrUndefined(polygon.extrudedHeight, time);
options.granularity = Property.getValueOrUndefined(polygon.granularity, time);
options.stRotation = Property.getValueOrUndefined(polygon.stRotation, time);
options.perPositionHeight = Property.getValueOrUndefined(polygon.perPositionHeight, time);
options.closeTop = closeTopValue;
options.closeBottom = closeBottomValue;
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
if (Property.getValueOrDefault(polygon.fill, time, true)) {
var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
this._material = material;
if (onTerrain) {
var currentColor = Color.WHITE;
if (defined(fillMaterialProperty.color)) {
currentColor = fillMaterialProperty.color.getValue(time);
}
this._primitive = groundPrimitives.add(new GroundPrimitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new PolygonGeometry(options),
attributes: {
color: ColorGeometryInstanceAttribute.fromColor(currentColor)
}
}),
asynchronous : false,
shadows : shadows
}));
} else {
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : defined(options.extrudedHeight) && options.extrudedHeight !== options.height && closeTopValue && closeBottomValue
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new PolygonGeometry(options)
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
}
if (!onTerrain && Property.getValueOrDefault(polygon.outline, time, false)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(polygon.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(polygon.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new PolygonOutlineGeometry(options),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (this._geometryUpdater._onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
}
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return PolygonGeometryUpdater;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PolylineColorAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 prevPosition3DHigh;\n\
attribute vec3 prevPosition3DLow;\n\
attribute vec3 nextPosition3DHigh;\n\
attribute vec3 nextPosition3DLow;\n\
attribute vec2 expandAndWidth;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
varying vec4 v_color;\n\
void main()\n\
{\n\
float expandDir = expandAndWidth.x;\n\
float width = abs(expandAndWidth.y) + 0.5;\n\
bool usePrev = expandAndWidth.y < 0.0;\n\
vec4 p = czm_computePosition();\n\
vec4 prev = czm_computePrevPosition();\n\
vec4 next = czm_computeNextPosition();\n\
v_color = color;\n\
vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev);\n\
gl_Position = czm_viewportOrthographic * positionWC;\n\
}\n\
";
});
/*global define*/
define('Scene/PolylineColorAppearance',[
'../Core/defaultValue',
'../Core/defineProperties',
'../Core/VertexFormat',
'../Shaders/Appearances/PerInstanceFlatColorAppearanceFS',
'../Shaders/Appearances/PolylineColorAppearanceVS',
'../Shaders/PolylineCommon',
'./Appearance'
], function(
defaultValue,
defineProperties,
VertexFormat,
PerInstanceFlatColorAppearanceFS,
PolylineColorAppearanceVS,
PolylineCommon,
Appearance) {
'use strict';
var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineColorAppearanceVS;
var defaultFragmentShaderSource = PerInstanceFlatColorAppearanceFS;
/**
* An appearance for {@link GeometryInstance} instances with color attributes and {@link PolylineGeometry}.
* This allows several geometry instances, each with a different color, to
* be drawn with the same {@link Primitive}.
*
* @alias PolylineColorAppearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.translucent=true] When true
, the geometry is expected to appear translucent so {@link PolylineColorAppearance#renderState} has alpha blending enabled.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @example
* // A solid white line segment
* var primitive = new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : new Cesium.PolylineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 0.0, 0.0,
* 5.0, 0.0
* ]),
* width : 10.0,
* vertexFormat : Cesium.PolylineColorAppearance.VERTEX_FORMAT
* }),
* attributes : {
* color : Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 1.0, 1.0, 1.0))
* }
* }),
* appearance : new Cesium.PolylineColorAppearance({
* translucent : false
* })
* });
*/
function PolylineColorAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var translucent = defaultValue(options.translucent, true);
var closed = false;
var vertexFormat = PolylineColorAppearance.VERTEX_FORMAT;
/**
* This property is part of the {@link Appearance} interface, but is not
* used by {@link PolylineColorAppearance} since a fully custom fragment shader is used.
*
* @type Material
*
* @default undefined
*/
this.material = undefined;
/**
* When true
, the geometry is expected to appear translucent so
* {@link PolylineColorAppearance#renderState} has alpha blending enabled.
*
* @type {Boolean}
*
* @default true
*/
this.translucent = translucent;
this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource);
this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
this._closed = closed;
// Non-derived members
this._vertexFormat = vertexFormat;
}
defineProperties(PolylineColorAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof PolylineColorAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader.
*
* @memberof PolylineColorAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* The render state can be explicitly defined when constructing a {@link PolylineColorAppearance}
* instance, or it is set implicitly via {@link PolylineColorAppearance#translucent}.
*
*
* @memberof PolylineColorAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed so
* {@link PolylineColorAppearance#renderState} has backface culling enabled.
* This is always false
for PolylineColorAppearance
.
*
* @memberof PolylineColorAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
},
/**
* The {@link VertexFormat} that this appearance instance is compatible with.
* A geometry can have more vertex attributes and still be compatible - at a
* potential performance cost - but it can't have less.
*
* @memberof PolylineColorAppearance.prototype
*
* @type VertexFormat
* @readonly
*
* @default {@link PolylineColorAppearance.VERTEX_FORMAT}
*/
vertexFormat : {
get : function() {
return this._vertexFormat;
}
}
});
/**
* The {@link VertexFormat} that all {@link PolylineColorAppearance} instances
* are compatible with. This requires only a position
attribute.
*
* @type VertexFormat
*
* @constant
*/
PolylineColorAppearance.VERTEX_FORMAT = VertexFormat.POSITION_ONLY;
/**
* Procedurally creates the full GLSL fragment shader source.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
PolylineColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link PolylineColorAppearance#translucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
PolylineColorAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
PolylineColorAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
return PolylineColorAppearance;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PolylineMaterialAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 prevPosition3DHigh;\n\
attribute vec3 prevPosition3DLow;\n\
attribute vec3 nextPosition3DHigh;\n\
attribute vec3 nextPosition3DLow;\n\
attribute vec2 expandAndWidth;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
varying float v_width;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
float expandDir = expandAndWidth.x;\n\
float width = abs(expandAndWidth.y) + 0.5;\n\
bool usePrev = expandAndWidth.y < 0.0;\n\
vec4 p = czm_computePosition();\n\
vec4 prev = czm_computePrevPosition();\n\
vec4 next = czm_computeNextPosition();\n\
v_width = width;\n\
v_st = st;\n\
vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev);\n\
gl_Position = czm_viewportOrthographic * positionWC;\n\
}\n\
";
});
/*global define*/
define('Scene/PolylineMaterialAppearance',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/VertexFormat',
'../Shaders/Appearances/PolylineMaterialAppearanceVS',
'../Shaders/PolylineCommon',
'../Shaders/PolylineFS',
'./Appearance',
'./Material'
], function(
defaultValue,
defined,
defineProperties,
VertexFormat,
PolylineMaterialAppearanceVS,
PolylineCommon,
PolylineFS,
Appearance,
Material) {
'use strict';
var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineMaterialAppearanceVS;
var defaultFragmentShaderSource = PolylineFS;
/**
* An appearance for {@link PolylineGeometry} that supports shading with materials.
*
* @alias PolylineMaterialAppearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.translucent=true] When true
, the geometry is expected to appear translucent so {@link PolylineMaterialAppearance#renderState} has alpha blending enabled.
* @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*
* @example
* var primitive = new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : new Cesium.PolylineGeometry({
* positions : Cesium.Cartesian3.fromDegreesArray([
* 0.0, 0.0,
* 5.0, 0.0
* ]),
* width : 10.0,
* vertexFormat : Cesium.PolylineMaterialAppearance.VERTEX_FORMAT
* })
* }),
* appearance : new Cesium.PolylineMaterialAppearance({
* material : Cesium.Material.fromType('Color')
* })
* });
*/
function PolylineMaterialAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var translucent = defaultValue(options.translucent, true);
var closed = false;
var vertexFormat = PolylineMaterialAppearance.VERTEX_FORMAT;
/**
* The material used to determine the fragment color. Unlike other {@link PolylineMaterialAppearance}
* properties, this is not read-only, so an appearance's material can change on the fly.
*
* @type Material
*
* @default {@link Material.ColorType}
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = defined(options.material) ? options.material : Material.fromType(Material.ColorType);
/**
* When true
, the geometry is expected to appear translucent so
* {@link PolylineMaterialAppearance#renderState} has alpha blending enabled.
*
* @type {Boolean}
*
* @default true
*/
this.translucent = translucent;
this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource);
this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
this._closed = closed;
// Non-derived members
this._vertexFormat = vertexFormat;
}
defineProperties(PolylineMaterialAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof PolylineMaterialAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader.
*
* @memberof PolylineMaterialAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* The render state can be explicitly defined when constructing a {@link PolylineMaterialAppearance}
* instance, or it is set implicitly via {@link PolylineMaterialAppearance#translucent}
* and {@link PolylineMaterialAppearance#closed}.
*
*
* @memberof PolylineMaterialAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed so
* {@link PolylineMaterialAppearance#renderState} has backface culling enabled.
* This is always false
for PolylineMaterialAppearance
.
*
* @memberof PolylineMaterialAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
},
/**
* The {@link VertexFormat} that this appearance instance is compatible with.
* A geometry can have more vertex attributes and still be compatible - at a
* potential performance cost - but it can't have less.
*
* @memberof PolylineMaterialAppearance.prototype
*
* @type VertexFormat
* @readonly
*
* @default {@link PolylineMaterialAppearance.VERTEX_FORMAT}
*/
vertexFormat : {
get : function() {
return this._vertexFormat;
}
}
});
/**
* The {@link VertexFormat} that all {@link PolylineMaterialAppearance} instances
* are compatible with. This requires position
and st
attributes.
*
* @type VertexFormat
*
* @constant
*/
PolylineMaterialAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_ST;
/**
* Procedurally creates the full GLSL fragment shader source. For {@link PolylineMaterialAppearance},
* this is derived from {@link PolylineMaterialAppearance#fragmentShaderSource} and {@link PolylineMaterialAppearance#material}.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
PolylineMaterialAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link PolylineMaterialAppearance#translucent} and {@link Material#isTranslucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
PolylineMaterialAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
PolylineMaterialAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
return PolylineMaterialAppearance;
});
/*global define*/
define('DataSources/PolylineGeometryUpdater',[
'../Core/BoundingSphere',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/PolylineGeometry',
'../Core/PolylinePipeline',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/PolylineCollection',
'../Scene/PolylineColorAppearance',
'../Scene/PolylineMaterialAppearance',
'../Scene/ShadowMode',
'./BoundingSphereState',
'./ColorMaterialProperty',
'./ConstantProperty',
'./MaterialProperty',
'./Property'
], function(
BoundingSphere,
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
PolylineGeometry,
PolylinePipeline,
ShowGeometryInstanceAttribute,
PolylineCollection,
PolylineColorAppearance,
PolylineMaterialAppearance,
ShadowMode,
BoundingSphereState,
ColorMaterialProperty,
ConstantProperty,
MaterialProperty,
Property) {
'use strict';
//We use this object to create one polyline collection per-scene.
var polylineCollections = {};
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.positions = undefined;
this.width = undefined;
this.followSurface = undefined;
this.granularity = undefined;
}
/**
* A {@link GeometryUpdater} for polylines.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias PolylineGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function PolylineGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(PolylineGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._dynamic = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'polyline', entity.polyline, undefined);
}
defineProperties(PolylineGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof PolylineGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PolylineColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof PolylineGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : PolylineMaterialAppearance
}
});
defineProperties(PolylineGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled || (!defined(this._entity.availability) && Property.isConstant(this._showProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
value : false
},
/**
* Gets a value indicating if outline visibility varies with simulation time.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
value : true
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
value : undefined
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
value : false
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof PolylineGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
PolylineGeometryUpdater.prototype.isOutlineVisible = function(time) {
return false;
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
PolylineGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
PolylineGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var color;
var attributes;
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new PolylineGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
PolylineGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
PolylineGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
PolylineGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
PolylineGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'polyline')) {
return;
}
var polyline = this._entity.polyline;
if (!defined(polyline)) {
if (this._fillEnabled) {
this._fillEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var positionsProperty = polyline.positions;
var show = polyline.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(positionsProperty))) {
if (this._fillEnabled) {
this._fillEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(polyline.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._showProperty = defaultValue(show, defaultShow);
this._shadowsProperty = defaultValue(polyline.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(polyline.distanceDisplayCondition, defaultDistanceDisplayCondition);
this._fillEnabled = true;
var width = polyline.width;
var followSurface = polyline.followSurface;
var granularity = polyline.granularity;
if (!positionsProperty.isConstant || !Property.isConstant(width) ||
!Property.isConstant(followSurface) || !Property.isConstant(granularity)) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
var positions = positionsProperty.getValue(Iso8601.MINIMUM_VALUE, options.positions);
//Because of the way we currently handle reference properties,
//we can't automatically assume the positions are always valid.
if (!defined(positions) || positions.length < 2) {
if (this._fillEnabled) {
this._fillEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
options.vertexFormat = isColorMaterial ? PolylineColorAppearance.VERTEX_FORMAT : PolylineMaterialAppearance.VERTEX_FORMAT;
options.positions = positions;
options.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.followSurface = defined(followSurface) ? followSurface.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
PolylineGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, this);
};
/**
* @private
*/
var generateCartesianArcOptions = {
positions : undefined,
granularity : undefined,
height : undefined,
ellipsoid : undefined
};
function DynamicGeometryUpdater(primitives, geometryUpdater) {
var sceneId = geometryUpdater._scene.id;
var polylineCollection = polylineCollections[sceneId];
if (!defined(polylineCollection) || polylineCollection.isDestroyed()) {
polylineCollection = new PolylineCollection();
polylineCollections[sceneId] = polylineCollection;
primitives.add(polylineCollection);
} else if (!primitives.contains(polylineCollection)) {
primitives.add(polylineCollection);
}
var line = polylineCollection.add();
line.id = geometryUpdater._entity;
this._line = line;
this._primitives = primitives;
this._geometryUpdater = geometryUpdater;
this._positions = [];
generateCartesianArcOptions.ellipsoid = geometryUpdater._scene.globe.ellipsoid;
}
DynamicGeometryUpdater.prototype.update = function(time) {
var geometryUpdater = this._geometryUpdater;
var entity = geometryUpdater._entity;
var polyline = entity.polyline;
var line = this._line;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) {
line.show = false;
return;
}
var positionsProperty = polyline.positions;
var positions = Property.getValueOrUndefined(positionsProperty, time, this._positions);
if (!defined(positions) || positions.length < 2) {
line.show = false;
return;
}
var followSurface = Property.getValueOrDefault(polyline._followSurface, time, true);
if (followSurface) {
generateCartesianArcOptions.positions = positions;
generateCartesianArcOptions.granularity = Property.getValueOrUndefined(polyline._granularity, time);
generateCartesianArcOptions.height = PolylinePipeline.extractHeights(positions, this._geometryUpdater._scene.globe.ellipsoid);
positions = PolylinePipeline.generateCartesianArc(generateCartesianArcOptions);
}
line.show = true;
line.positions = positions.slice();
line.material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, line.material);
line.width = Property.getValueOrDefault(polyline._width, time, 1);
line.distanceDisplayCondition = Property.getValueOrUndefined(polyline._distanceDisplayCondition, time, line.distanceDisplayCondition);
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
var line = this._line;
if (line.show && line.positions.length > 0) {
BoundingSphere.fromPoints(line.positions, result);
return BoundingSphereState.DONE;
}
return BoundingSphereState.FAILED;
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var geometryUpdater = this._geometryUpdater;
var sceneId = geometryUpdater._scene.id;
var polylineCollection = polylineCollections[sceneId];
polylineCollection.remove(this._line);
if (polylineCollection.length === 0) {
this._primitives.removeAndDestroy(polylineCollection);
delete polylineCollections[sceneId];
}
destroyObject(this);
};
return PolylineGeometryUpdater;
});
/*global define*/
define('DataSources/PolylineVolumeGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/PolylineVolumeGeometry',
'../Core/PolylineVolumeOutlineGeometry',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
PolylineVolumeGeometry,
PolylineVolumeOutlineGeometry,
ShowGeometryInstanceAttribute,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.polylinePositions = undefined;
this.shapePositions = undefined;
this.cornerType = undefined;
this.granularity = undefined;
}
/**
* A {@link GeometryUpdater} for polyline volumes.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias PolylineVolumeGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function PolylineVolumeGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(PolylineVolumeGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'polylineVolume', entity.polylineVolume, undefined);
}
defineProperties(PolylineVolumeGeometryUpdater, {
/**
* Gets the type of appearance to use for simple color-based geometry.
* @memberof PolylineVolumeGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of appearance to use for material-based geometry.
* @memberof PolylineVolumeGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(PolylineVolumeGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
value : true
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof PolylineVolumeGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
PolylineVolumeGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
PolylineVolumeGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
PolylineVolumeGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new PolylineVolumeGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
PolylineVolumeGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new PolylineVolumeOutlineGeometry(this._options),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
PolylineVolumeGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
PolylineVolumeGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
PolylineVolumeGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'polylineVolume')) {
return;
}
var polylineVolume = this._entity.polylineVolume;
if (!defined(polylineVolume)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = polylineVolume.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = polylineVolume.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var positions = polylineVolume.positions;
var shape = polylineVolume.shape;
var show = polylineVolume.show;
if (!defined(positions) || !defined(shape) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(polylineVolume.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(polylineVolume.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(polylineVolume.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(polylineVolume.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(polylineVolume.distanceDisplayCondition, defaultDistanceDisplayCondition);
var granularity = polylineVolume.granularity;
var outlineWidth = polylineVolume.outlineWidth;
var cornerType = polylineVolume.cornerType;
this._fillEnabled = fillEnabled;
this._outlineEnabled = outlineEnabled;
if (!positions.isConstant || //
!shape.isConstant || //
!Property.isConstant(granularity) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(cornerType)) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.polylinePositions = positions.getValue(Iso8601.MINIMUM_VALUE, options.polylinePositions);
options.shapePositions = shape.getValue(Iso8601.MINIMUM_VALUE, options.shape);
options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.cornerType = defined(cornerType) ? cornerType.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
PolylineVolumeGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, geometryUpdater) {
this._primitives = primitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._primitive = undefined;
this._outlinePrimitive = undefined;
var geometryUpdater = this._geometryUpdater;
var entity = geometryUpdater._entity;
var polylineVolume = entity.polylineVolume;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polylineVolume.show, time, true)) {
return;
}
var options = this._options;
var positions = Property.getValueOrUndefined(polylineVolume.positions, time, options.polylinePositions);
var shape = Property.getValueOrUndefined(polylineVolume.shape, time);
if (!defined(positions) || !defined(shape)) {
return;
}
options.polylinePositions = positions;
options.shapePositions = shape;
options.granularity = Property.getValueOrUndefined(polylineVolume.granularity, time);
options.cornerType = Property.getValueOrUndefined(polylineVolume.cornerType, time);
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
if (!defined(polylineVolume.fill) || polylineVolume.fill.getValue(time)) {
var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
this._material = material;
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : true
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new PolylineVolumeGeometry(options)
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
if (defined(polylineVolume.outline) && polylineVolume.outline.getValue(time)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(polylineVolume.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(polylineVolume.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new PolylineVolumeOutlineGeometry(options),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return PolylineVolumeGeometryUpdater;
});
/*global define*/
define('DataSources/RectangleGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/oneTimeWarning',
'../Core/RectangleGeometry',
'../Core/RectangleOutlineGeometry',
'../Core/ShowGeometryInstanceAttribute',
'../Scene/GroundPrimitive',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
oneTimeWarning,
RectangleGeometry,
RectangleOutlineGeometry,
ShowGeometryInstanceAttribute,
GroundPrimitive,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.rectangle = undefined;
this.closeBottom = undefined;
this.closeTop = undefined;
this.height = undefined;
this.extrudedHeight = undefined;
this.granularity = undefined;
this.stRotation = undefined;
this.rotation = undefined;
}
/**
* A {@link GeometryUpdater} for rectangles.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias RectangleGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function RectangleGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(RectangleGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._isClosed = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._onTerrain = false;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'rectangle', entity.rectangle, undefined);
}
defineProperties(RectangleGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof RectangleGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof RectangleGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(RectangleGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
get : function() {
return this._isClosed;
}
},
/**
* Gets a value indicating if the geometry should be drawn on terrain.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
onTerrain : {
get : function() {
return this._onTerrain;
}
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof RectangleGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
RectangleGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
RectangleGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
RectangleGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new RectangleGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
RectangleGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new RectangleOutlineGeometry(this._options),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
RectangleGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
RectangleGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
RectangleGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'rectangle')) {
return;
}
var rectangle = this._entity.rectangle;
if (!defined(rectangle)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = rectangle.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = rectangle.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var coordinates = rectangle.coordinates;
var show = rectangle.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(coordinates))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(rectangle.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(rectangle.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(rectangle.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(rectangle.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(rectangle.distanceDisplayCondition, defaultDistanceDisplayCondition);
var height = rectangle.height;
var extrudedHeight = rectangle.extrudedHeight;
var granularity = rectangle.granularity;
var stRotation = rectangle.stRotation;
var rotation = rectangle.rotation;
var outlineWidth = rectangle.outlineWidth;
var closeBottom = rectangle.closeBottom;
var closeTop = rectangle.closeTop;
var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) &&
isColorMaterial && GroundPrimitive.isSupported(this._scene);
if (outlineEnabled && onTerrain) {
oneTimeWarning(oneTimeWarning.geometryOutlines);
outlineEnabled = false;
}
this._fillEnabled = fillEnabled;
this._onTerrain = onTerrain;
this._outlineEnabled = outlineEnabled;
if (!coordinates.isConstant || //
!Property.isConstant(height) || //
!Property.isConstant(extrudedHeight) || //
!Property.isConstant(granularity) || //
!Property.isConstant(stRotation) || //
!Property.isConstant(rotation) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(closeBottom) || //
!Property.isConstant(closeTop) || //
(onTerrain && !Property.isConstant(material))) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.rectangle = coordinates.getValue(Iso8601.MINIMUM_VALUE, options.rectangle);
options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.stRotation = defined(stRotation) ? stRotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.rotation = defined(rotation) ? rotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.closeBottom = defined(closeBottom) ? closeBottom.getValue(Iso8601.MINIMUM_VALUE) : undefined;
options.closeTop = defined(closeTop) ? closeTop.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._isClosed = defined(extrudedHeight) && defined(options.closeTop) && defined(options.closeBottom) && options.closeTop && options.closeBottom;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @param {PrimitiveCollection} groundPrimitives The primitive collection to use for GroundPrimitives.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
RectangleGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
this._primitives = primitives;
this._groundPrimitives = groundPrimitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var geometryUpdater = this._geometryUpdater;
var onTerrain = geometryUpdater._onTerrain;
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._outlinePrimitive = undefined;
}
this._primitive = undefined;
var entity = geometryUpdater._entity;
var rectangle = entity.rectangle;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(rectangle.show, time, true)) {
return;
}
var options = this._options;
var coordinates = Property.getValueOrUndefined(rectangle.coordinates, time, options.rectangle);
if (!defined(coordinates)) {
return;
}
options.rectangle = coordinates;
options.height = Property.getValueOrUndefined(rectangle.height, time);
options.extrudedHeight = Property.getValueOrUndefined(rectangle.extrudedHeight, time);
options.granularity = Property.getValueOrUndefined(rectangle.granularity, time);
options.stRotation = Property.getValueOrUndefined(rectangle.stRotation, time);
options.rotation = Property.getValueOrUndefined(rectangle.rotation, time);
options.closeBottom = Property.getValueOrUndefined(rectangle.closeBottom, time);
options.closeTop = Property.getValueOrUndefined(rectangle.closeTop, time);
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
if (Property.getValueOrDefault(rectangle.fill, time, true)) {
var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
this._material = material;
if (onTerrain) {
var currentColor = Color.WHITE;
if (defined(fillMaterialProperty.color)) {
currentColor = fillMaterialProperty.color.getValue(time);
}
this._primitive = groundPrimitives.add(new GroundPrimitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new RectangleGeometry(options),
attributes: {
color: ColorGeometryInstanceAttribute.fromColor(currentColor)
}
}),
asynchronous : false,
shadows : shadows
}));
} else {
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : defined(options.extrudedHeight)
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new RectangleGeometry(options)
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
}
if (!onTerrain && Property.getValueOrDefault(rectangle.outline, time, false)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(rectangle.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(rectangle.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new RectangleOutlineGeometry(options),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
var groundPrimitives = this._groundPrimitives;
if (this._geometryUpdater._onTerrain) {
groundPrimitives.removeAndDestroy(this._primitive);
} else {
primitives.removeAndDestroy(this._primitive);
}
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return RectangleGeometryUpdater;
});
/*global define*/
define('DataSources/WallGeometryUpdater',[
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/DistanceDisplayCondition',
'../Core/DistanceDisplayConditionGeometryInstanceAttribute',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/Iso8601',
'../Core/ShowGeometryInstanceAttribute',
'../Core/WallGeometry',
'../Core/WallOutlineGeometry',
'../Scene/MaterialAppearance',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'../Scene/ShadowMode',
'./ColorMaterialProperty',
'./ConstantProperty',
'./dynamicGeometryGetBoundingSphere',
'./MaterialProperty',
'./Property'
], function(
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
DistanceDisplayCondition,
DistanceDisplayConditionGeometryInstanceAttribute,
Event,
GeometryInstance,
Iso8601,
ShowGeometryInstanceAttribute,
WallGeometry,
WallOutlineGeometry,
MaterialAppearance,
PerInstanceColorAppearance,
Primitive,
ShadowMode,
ColorMaterialProperty,
ConstantProperty,
dynamicGeometryGetBoundingSphere,
MaterialProperty,
Property) {
'use strict';
var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
var defaultShow = new ConstantProperty(true);
var defaultFill = new ConstantProperty(true);
var defaultOutline = new ConstantProperty(false);
var defaultOutlineColor = new ConstantProperty(Color.BLACK);
var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
var scratchColor = new Color();
function GeometryOptions(entity) {
this.id = entity;
this.vertexFormat = undefined;
this.positions = undefined;
this.minimumHeights = undefined;
this.maximumHeights = undefined;
this.granularity = undefined;
}
/**
* A {@link GeometryUpdater} for walls.
* Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
* @alias WallGeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*/
function WallGeometryUpdater(entity, scene) {
if (!defined(entity)) {
throw new DeveloperError('entity is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
this._entity = entity;
this._scene = scene;
this._entitySubscription = entity.definitionChanged.addEventListener(WallGeometryUpdater.prototype._onEntityPropertyChanged, this);
this._fillEnabled = false;
this._dynamic = false;
this._outlineEnabled = false;
this._geometryChanged = new Event();
this._showProperty = undefined;
this._materialProperty = undefined;
this._hasConstantOutline = true;
this._showOutlineProperty = undefined;
this._outlineColorProperty = undefined;
this._outlineWidth = 1.0;
this._shadowsProperty = undefined;
this._distanceDisplayConditionProperty = undefined;
this._options = new GeometryOptions(entity);
this._onEntityPropertyChanged(entity, 'wall', entity.wall, undefined);
}
defineProperties(WallGeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof WallGeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
value : PerInstanceColorAppearance
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof WallGeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
value : MaterialAppearance
}
});
defineProperties(WallGeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof WallGeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : function() {
return this._entity;
}
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : function() {
return this._fillEnabled;
}
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : function() {
return !this._fillEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._fillProperty));
}
},
/**
* Gets the material property used to fill the geometry.
* @memberof WallGeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : function() {
return this._materialProperty;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : function() {
return this._outlineEnabled;
}
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : function() {
return !this._outlineEnabled ||
(!defined(this._entity.availability) &&
Property.isConstant(this._showProperty) &&
Property.isConstant(this._showOutlineProperty));
}
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof WallGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : function() {
return this._outlineColorProperty;
}
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof WallGeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : function() {
return this._outlineWidth;
}
},
/**
* Gets the property specifying whether the geometry
* casts or receives shadows from each light source.
* @memberof WallGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
shadowsProperty : {
get : function() {
return this._shadowsProperty;
}
},
/**
* Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
* @memberof WallGeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
distanceDisplayConditionProperty : {
get : function() {
return this._distanceDisplayConditionProperty;
}
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : function() {
return this._dynamic;
}
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
get : function() {
return false;
}
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof WallGeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : function() {
return this._geometryChanged;
}
}
});
/**
* Checks if the geometry is outlined at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
WallGeometryUpdater.prototype.isOutlineVisible = function(time) {
var entity = this._entity;
return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
};
/**
* Checks if the geometry is filled at the provided time.
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
WallGeometryUpdater.prototype.isFilled = function(time) {
var entity = this._entity;
return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
};
/**
* Creates the geometry instance which represents the fill of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
WallGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._fillEnabled) {
throw new DeveloperError('This instance does not represent a filled geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var attributes;
var color;
var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
if (this._materialProperty instanceof ColorMaterialProperty) {
var currentColor = Color.WHITE;
if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
currentColor = this._materialProperty.color.getValue(time);
}
color = ColorGeometryInstanceAttribute.fromColor(currentColor);
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute,
color : color
};
} else {
attributes = {
show : show,
distanceDisplayCondition : distanceDisplayConditionAttribute
};
}
return new GeometryInstance({
id : entity,
geometry : new WallGeometry(this._options),
attributes : attributes
});
};
/**
* Creates the geometry instance which represents the outline of the geometry.
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
WallGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!this._outlineEnabled) {
throw new DeveloperError('This instance does not represent an outlined geometry.');
}
var entity = this._entity;
var isAvailable = entity.isAvailable(time);
var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
return new GeometryInstance({
id : entity,
geometry : new WallOutlineGeometry(this._options),
attributes : {
show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
}
});
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
WallGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
WallGeometryUpdater.prototype.destroy = function() {
this._entitySubscription();
destroyObject(this);
};
WallGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
if (!(propertyName === 'availability' || propertyName === 'wall')) {
return;
}
var wall = this._entity.wall;
if (!defined(wall)) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var fillProperty = wall.fill;
var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;
var outlineProperty = wall.outline;
var outlineEnabled = defined(outlineProperty);
if (outlineEnabled && outlineProperty.isConstant) {
outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
}
if (!fillEnabled && !outlineEnabled) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var positions = wall.positions;
var show = wall.show;
if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
(!defined(positions))) {
if (this._fillEnabled || this._outlineEnabled) {
this._fillEnabled = false;
this._outlineEnabled = false;
this._geometryChanged.raiseEvent(this);
}
return;
}
var material = defaultValue(wall.material, defaultMaterial);
var isColorMaterial = material instanceof ColorMaterialProperty;
this._materialProperty = material;
this._fillProperty = defaultValue(fillProperty, defaultFill);
this._showProperty = defaultValue(show, defaultShow);
this._showOutlineProperty = defaultValue(wall.outline, defaultOutline);
this._outlineColorProperty = outlineEnabled ? defaultValue(wall.outlineColor, defaultOutlineColor) : undefined;
this._shadowsProperty = defaultValue(wall.shadows, defaultShadows);
this._distanceDisplayConditionProperty = defaultValue(wall.distanceDisplayCondition, defaultDistanceDisplayCondition);
var minimumHeights = wall.minimumHeights;
var maximumHeights = wall.maximumHeights;
var outlineWidth = wall.outlineWidth;
var granularity = wall.granularity;
this._fillEnabled = fillEnabled;
this._outlineEnabled = outlineEnabled;
if (!positions.isConstant || //
!Property.isConstant(minimumHeights) || //
!Property.isConstant(maximumHeights) || //
!Property.isConstant(outlineWidth) || //
!Property.isConstant(granularity)) {
if (!this._dynamic) {
this._dynamic = true;
this._geometryChanged.raiseEvent(this);
}
} else {
var options = this._options;
options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
options.positions = positions.getValue(Iso8601.MINIMUM_VALUE, options.positions);
options.minimumHeights = defined(minimumHeights) ? minimumHeights.getValue(Iso8601.MINIMUM_VALUE, options.minimumHeights) : undefined;
options.maximumHeights = defined(maximumHeights) ? maximumHeights.getValue(Iso8601.MINIMUM_VALUE, options.maximumHeights) : undefined;
options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
this._dynamic = false;
this._geometryChanged.raiseEvent(this);
}
};
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
WallGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
if (!this._dynamic) {
throw new DeveloperError('This instance does not represent dynamic geometry.');
}
if (!defined(primitives)) {
throw new DeveloperError('primitives is required.');
}
return new DynamicGeometryUpdater(primitives, this);
};
/**
* @private
*/
function DynamicGeometryUpdater(primitives, geometryUpdater) {
this._primitives = primitives;
this._primitive = undefined;
this._outlinePrimitive = undefined;
this._geometryUpdater = geometryUpdater;
this._options = new GeometryOptions(geometryUpdater._entity);
}
DynamicGeometryUpdater.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
this._primitive = undefined;
this._outlinePrimitive = undefined;
var geometryUpdater = this._geometryUpdater;
var entity = geometryUpdater._entity;
var wall = entity.wall;
if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(wall.show, time, true)) {
return;
}
var options = this._options;
var positions = Property.getValueOrUndefined(wall.positions, time, options.positions);
if (!defined(positions)) {
return;
}
options.positions = positions;
options.minimumHeights = Property.getValueOrUndefined(wall.minimumHeights, time, options.minimumHeights);
options.maximumHeights = Property.getValueOrUndefined(wall.maximumHeights, time, options.maximumHeights);
options.granularity = Property.getValueOrUndefined(wall.granularity, time);
var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
if (Property.getValueOrDefault(wall.fill, time, true)) {
var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
this._material = material;
var appearance = new MaterialAppearance({
material : material,
translucent : material.isTranslucent(),
closed : defined(options.extrudedHeight)
});
options.vertexFormat = appearance.vertexFormat;
this._primitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new WallGeometry(options)
}),
appearance : appearance,
asynchronous : false,
shadows : shadows
}));
}
if (Property.getValueOrDefault(wall.outline, time, false)) {
options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;
var outlineColor = Property.getValueOrClonedDefault(wall.outlineColor, time, Color.BLACK, scratchColor);
var outlineWidth = Property.getValueOrDefault(wall.outlineWidth, time, 1.0);
var translucent = outlineColor.alpha !== 1.0;
this._outlinePrimitive = primitives.add(new Primitive({
geometryInstances : new GeometryInstance({
id : entity,
geometry : new WallOutlineGeometry(options),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : translucent,
renderState : {
lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
}
}),
asynchronous : false,
shadows : shadows
}));
}
};
DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
};
DynamicGeometryUpdater.prototype.isDestroyed = function() {
return false;
};
DynamicGeometryUpdater.prototype.destroy = function() {
var primitives = this._primitives;
primitives.removeAndDestroy(this._primitive);
primitives.removeAndDestroy(this._outlinePrimitive);
destroyObject(this);
};
return WallGeometryUpdater;
});
/*global define*/
define('DataSources/DataSourceDisplay',[
'../Core/BoundingSphere',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EventHelper',
'../Scene/GroundPrimitive',
'./BillboardVisualizer',
'./BoundingSphereState',
'./BoxGeometryUpdater',
'./CorridorGeometryUpdater',
'./CustomDataSource',
'./CylinderGeometryUpdater',
'./EllipseGeometryUpdater',
'./EllipsoidGeometryUpdater',
'./GeometryVisualizer',
'./LabelVisualizer',
'./ModelVisualizer',
'./PathVisualizer',
'./PointVisualizer',
'./PolygonGeometryUpdater',
'./PolylineGeometryUpdater',
'./PolylineVolumeGeometryUpdater',
'./RectangleGeometryUpdater',
'./WallGeometryUpdater'
], function(
BoundingSphere,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EventHelper,
GroundPrimitive,
BillboardVisualizer,
BoundingSphereState,
BoxGeometryUpdater,
CorridorGeometryUpdater,
CustomDataSource,
CylinderGeometryUpdater,
EllipseGeometryUpdater,
EllipsoidGeometryUpdater,
GeometryVisualizer,
LabelVisualizer,
ModelVisualizer,
PathVisualizer,
PointVisualizer,
PolygonGeometryUpdater,
PolylineGeometryUpdater,
PolylineVolumeGeometryUpdater,
RectangleGeometryUpdater,
WallGeometryUpdater) {
'use strict';
/**
* Visualizes a collection of {@link DataSource} instances.
* @alias DataSourceDisplay
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Scene} options.scene The scene in which to display the data.
* @param {DataSourceCollection} options.dataSourceCollection The data sources to display.
* @param {DataSourceDisplay~VisualizersCallback} [options.visualizersCallback=DataSourceDisplay.defaultVisualizersCallback]
* A function which creates an array of visualizers used for visualization.
* If undefined, all standard visualizers are used.
*/
function DataSourceDisplay(options) {
if (!defined(options)) {
throw new DeveloperError('options is required.');
}
if (!defined(options.scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(options.dataSourceCollection)) {
throw new DeveloperError('dataSourceCollection is required.');
}
GroundPrimitive.initializeTerrainHeights();
var scene = options.scene;
var dataSourceCollection = options.dataSourceCollection;
this._eventHelper = new EventHelper();
this._eventHelper.add(dataSourceCollection.dataSourceAdded, this._onDataSourceAdded, this);
this._eventHelper.add(dataSourceCollection.dataSourceRemoved, this._onDataSourceRemoved, this);
this._dataSourceCollection = dataSourceCollection;
this._scene = scene;
this._visualizersCallback = defaultValue(options.visualizersCallback, DataSourceDisplay.defaultVisualizersCallback);
for (var i = 0, len = dataSourceCollection.length; i < len; i++) {
this._onDataSourceAdded(dataSourceCollection, dataSourceCollection.get(i));
}
var defaultDataSource = new CustomDataSource();
this._onDataSourceAdded(undefined, defaultDataSource);
this._defaultDataSource = defaultDataSource;
this._ready = false;
}
/**
* Gets or sets the default function which creates an array of visualizers used for visualization.
* By default, this function uses all standard visualizers.
*
* @member
* @type {DataSourceDisplay~VisualizersCallback}
*/
DataSourceDisplay.defaultVisualizersCallback = function(scene, entityCluster, dataSource) {
var entities = dataSource.entities;
return [new BillboardVisualizer(entityCluster, entities),
new GeometryVisualizer(BoxGeometryUpdater, scene, entities),
new GeometryVisualizer(CylinderGeometryUpdater, scene, entities),
new GeometryVisualizer(CorridorGeometryUpdater, scene, entities),
new GeometryVisualizer(EllipseGeometryUpdater, scene, entities),
new GeometryVisualizer(EllipsoidGeometryUpdater, scene, entities),
new GeometryVisualizer(PolygonGeometryUpdater, scene, entities),
new GeometryVisualizer(PolylineGeometryUpdater, scene, entities),
new GeometryVisualizer(PolylineVolumeGeometryUpdater, scene, entities),
new GeometryVisualizer(RectangleGeometryUpdater, scene, entities),
new GeometryVisualizer(WallGeometryUpdater, scene, entities),
new LabelVisualizer(entityCluster, entities),
new ModelVisualizer(scene, entities),
new PointVisualizer(entityCluster, entities),
new PathVisualizer(scene, entities)];
};
defineProperties(DataSourceDisplay.prototype, {
/**
* Gets the scene associated with this display.
* @memberof DataSourceDisplay.prototype
* @type {Scene}
*/
scene : {
get : function() {
return this._scene;
}
},
/**
* Gets the collection of data sources to display.
* @memberof DataSourceDisplay.prototype
* @type {DataSourceCollection}
*/
dataSources : {
get : function() {
return this._dataSourceCollection;
}
},
/**
* Gets the default data source instance which can be used to
* manually create and visualize entities not tied to
* a specific data source. This instance is always available
* and does not appear in the list dataSources collection.
* @memberof DataSourceDisplay.prototype
* @type {CustomDataSource}
*/
defaultDataSource : {
get : function() {
return this._defaultDataSource;
}
},
/**
* Gets a value indicating whether or not all entities in the data source are ready
* @memberof DataSourceDisplay.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
}
});
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see DataSourceDisplay#destroy
*/
DataSourceDisplay.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* dataSourceDisplay = dataSourceDisplay.destroy();
*
* @see DataSourceDisplay#isDestroyed
*/
DataSourceDisplay.prototype.destroy = function() {
this._eventHelper.removeAll();
var dataSourceCollection = this._dataSourceCollection;
for (var i = 0, length = dataSourceCollection.length; i < length; ++i) {
this._onDataSourceRemoved(this._dataSourceCollection, dataSourceCollection.get(i));
}
this._onDataSourceRemoved(undefined, this._defaultDataSource);
return destroyObject(this);
};
/**
* Updates the display to the provided time.
*
* @param {JulianDate} time The simulation time.
* @returns {Boolean} True if all data sources are ready to be displayed, false otherwise.
*/
DataSourceDisplay.prototype.update = function(time) {
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!GroundPrimitive._initialized) {
this._ready = false;
return false;
}
var result = true;
var i;
var x;
var visualizers;
var vLength;
var dataSources = this._dataSourceCollection;
var length = dataSources.length;
for (i = 0; i < length; i++) {
var dataSource = dataSources.get(i);
if (defined(dataSource.update)) {
result = dataSource.update(time) && result;
}
visualizers = dataSource._visualizers;
vLength = visualizers.length;
for (x = 0; x < vLength; x++) {
result = visualizers[x].update(time) && result;
}
}
visualizers = this._defaultDataSource._visualizers;
vLength = visualizers.length;
for (x = 0; x < vLength; x++) {
result = visualizers[x].update(time) && result;
}
this._ready = result;
return result;
};
var getBoundingSphereArrayScratch = [];
var getBoundingSphereBoundingSphereScratch = new BoundingSphere();
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {Boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
* If false, the the function will halt and return pending if any of the bounding spheres are pending.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
DataSourceDisplay.prototype.getBoundingSphere = function(entity, allowPartial, result) {
if (!defined(entity)) {
throw new DeveloperError('entity is required.');
}
if (!defined(allowPartial)) {
throw new DeveloperError('allowPartial is required.');
}
if (!defined(result)) {
throw new DeveloperError('result is required.');
}
if (!this._ready) {
return BoundingSphereState.PENDING;
}
var i;
var length;
var dataSource = this._defaultDataSource;
if (!dataSource.entities.contains(entity)) {
dataSource = undefined;
var dataSources = this._dataSourceCollection;
length = dataSources.length;
for (i = 0; i < length; i++) {
var d = dataSources.get(i);
if (d.entities.contains(entity)) {
dataSource = d;
break;
}
}
}
if (!defined(dataSource)) {
return BoundingSphereState.FAILED;
}
var boundingSpheres = getBoundingSphereArrayScratch;
var tmp = getBoundingSphereBoundingSphereScratch;
var count = 0;
var state = BoundingSphereState.DONE;
var visualizers = dataSource._visualizers;
var visualizersLength = visualizers.length;
for (i = 0; i < visualizersLength; i++) {
var visualizer = visualizers[i];
if (defined(visualizer.getBoundingSphere)) {
state = visualizers[i].getBoundingSphere(entity, tmp);
if (!allowPartial && state === BoundingSphereState.PENDING) {
return BoundingSphereState.PENDING;
} else if (state === BoundingSphereState.DONE) {
boundingSpheres[count] = BoundingSphere.clone(tmp, boundingSpheres[count]);
count++;
}
}
}
if (count === 0) {
return BoundingSphereState.FAILED;
}
boundingSpheres.length = count;
BoundingSphere.fromBoundingSpheres(boundingSpheres, result);
return BoundingSphereState.DONE;
};
DataSourceDisplay.prototype._onDataSourceAdded = function(dataSourceCollection, dataSource) {
var scene = this._scene;
var entityCluster = dataSource.clustering;
entityCluster._initialize(scene);
scene.primitives.add(entityCluster);
dataSource._visualizers = this._visualizersCallback(scene, entityCluster, dataSource);
};
DataSourceDisplay.prototype._onDataSourceRemoved = function(dataSourceCollection, dataSource) {
var scene = this._scene;
var entityCluster = dataSource.clustering;
scene.primitives.remove(entityCluster);
var visualizers = dataSource._visualizers;
var length = visualizers.length;
for (var i = 0; i < length; i++) {
visualizers[i].destroy();
}
dataSource._visualizers = undefined;
};
/**
* A function which creates an array of visualizers used for visualization.
* @callback DataSourceDisplay~VisualizersCallback
*
* @param {Scene} scene The scene to create visualizers for.
* @param {DataSource} dataSource The data source to create visualizers for.
* @returns {Visualizer[]} An array of visualizers used for visualization.
*
* @example
* function createVisualizers(scene, dataSource) {
* return [new Cesium.BillboardVisualizer(scene, dataSource.entities)];
* }
*/
return DataSourceDisplay;
});
/*global define*/
define('DataSources/DynamicGeometryUpdater',[
'../Core/DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* Defines the interface for a dynamic geometry updater. A DynamicGeometryUpdater
* is responsible for handling visualization of a specific type of geometry
* that needs to be recomputed based on simulation time.
* This object is never used directly by client code, but is instead created by
* {@link GeometryUpdater} implementations which contain dynamic geometry.
*
* This type defines an interface and cannot be instantiated directly.
*
* @alias DynamicGeometryUpdater
* @constructor
*/
function DynamicGeometryUpdater() {
DeveloperError.throwInstantiationError();
}
/**
* Updates the geometry to the specified time.
* @memberof DynamicGeometryUpdater
* @function
*
* @param {JulianDate} time The current time.
*/
DynamicGeometryUpdater.prototype.update = DeveloperError.throwInstantiationError;
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
* @function
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
DynamicGeometryUpdater.prototype.getBoundingSphere = DeveloperError.throwInstantiationError;
/**
* Returns true if this object was destroyed; otherwise, false.
* @memberof DynamicGeometryUpdater
* @function
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
DynamicGeometryUpdater.prototype.isDestroyed = DeveloperError.throwInstantiationError;
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
* @memberof DynamicGeometryUpdater
* @function
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
DynamicGeometryUpdater.prototype.destroy = DeveloperError.throwInstantiationError;
return DynamicGeometryUpdater;
});
/*global define*/
define('DataSources/EntityView',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/HeadingPitchRange',
'../Core/JulianDate',
'../Core/Math',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/Transforms',
'../Scene/SceneMode'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
HeadingPitchRange,
JulianDate,
CesiumMath,
Matrix3,
Matrix4,
Transforms,
SceneMode) {
'use strict';
var updateTransformMatrix3Scratch1 = new Matrix3();
var updateTransformMatrix3Scratch2 = new Matrix3();
var updateTransformMatrix3Scratch3 = new Matrix3();
var updateTransformMatrix4Scratch = new Matrix4();
var updateTransformCartesian3Scratch1 = new Cartesian3();
var updateTransformCartesian3Scratch2 = new Cartesian3();
var updateTransformCartesian3Scratch3 = new Cartesian3();
var updateTransformCartesian3Scratch4 = new Cartesian3();
var updateTransformCartesian3Scratch5 = new Cartesian3();
var updateTransformCartesian3Scratch6 = new Cartesian3();
var deltaTime = new JulianDate();
var northUpAxisFactor = 1.25; // times ellipsoid's maximum radius
function updateTransform(that, camera, updateLookAt, saveCamera, positionProperty, time, ellipsoid) {
var mode = that.scene.mode;
var cartesian = positionProperty.getValue(time, that._lastCartesian);
if (defined(cartesian)) {
var hasBasis = false;
var xBasis;
var yBasis;
var zBasis;
if (mode === SceneMode.SCENE3D) {
// The time delta was determined based on how fast satellites move compared to vehicles near the surface.
// Slower moving vehicles will most likely default to east-north-up, while faster ones will be VVLH.
deltaTime = JulianDate.addSeconds(time, 0.001, deltaTime);
var deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1);
if (defined(deltaCartesian)) {
var toInertial = Transforms.computeFixedToIcrfMatrix(time, updateTransformMatrix3Scratch1);
var toInertialDelta = Transforms.computeFixedToIcrfMatrix(deltaTime, updateTransformMatrix3Scratch2);
var toFixed;
if (!defined(toInertial) || !defined(toInertialDelta)) {
toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, updateTransformMatrix3Scratch3);
toInertial = Matrix3.transpose(toFixed, updateTransformMatrix3Scratch1);
toInertialDelta = Transforms.computeTemeToPseudoFixedMatrix(deltaTime, updateTransformMatrix3Scratch2);
Matrix3.transpose(toInertialDelta, toInertialDelta);
} else {
toFixed = Matrix3.transpose(toInertial, updateTransformMatrix3Scratch3);
}
var inertialCartesian = Matrix3.multiplyByVector(toInertial, cartesian, updateTransformCartesian3Scratch5);
var inertialDeltaCartesian = Matrix3.multiplyByVector(toInertialDelta, deltaCartesian, updateTransformCartesian3Scratch6);
Cartesian3.subtract(inertialCartesian, inertialDeltaCartesian, updateTransformCartesian3Scratch4);
var inertialVelocity = Cartesian3.magnitude(updateTransformCartesian3Scratch4) * 1000.0; // meters/sec
// http://en.wikipedia.org/wiki/Standard_gravitational_parameter
// Consider adding this to Cesium.Ellipsoid?
var mu = 3.986004418e14; // m^3 / sec^2
var semiMajorAxis = -mu / (inertialVelocity * inertialVelocity - (2 * mu / Cartesian3.magnitude(inertialCartesian)));
if (semiMajorAxis < 0 || semiMajorAxis > northUpAxisFactor * ellipsoid.maximumRadius) {
// North-up viewing from deep space.
// X along the nadir
xBasis = updateTransformCartesian3Scratch2;
Cartesian3.normalize(cartesian, xBasis);
Cartesian3.negate(xBasis, xBasis);
// Z is North
zBasis = Cartesian3.clone(Cartesian3.UNIT_Z, updateTransformCartesian3Scratch3);
// Y is along the cross of z and x (right handed basis / in the direction of motion)
yBasis = Cartesian3.cross(zBasis, xBasis, updateTransformCartesian3Scratch1);
if (Cartesian3.magnitude(yBasis) > CesiumMath.EPSILON7) {
Cartesian3.normalize(xBasis, xBasis);
Cartesian3.normalize(yBasis, yBasis);
zBasis = Cartesian3.cross(xBasis, yBasis, updateTransformCartesian3Scratch3);
Cartesian3.normalize(zBasis, zBasis);
hasBasis = true;
}
} else if (!Cartesian3.equalsEpsilon(cartesian, deltaCartesian, CesiumMath.EPSILON7)) {
// Approximation of VVLH (Vehicle Velocity Local Horizontal) with the Z-axis flipped.
// Z along the position
zBasis = updateTransformCartesian3Scratch2;
Cartesian3.normalize(inertialCartesian, zBasis);
Cartesian3.normalize(inertialDeltaCartesian, inertialDeltaCartesian);
// Y is along the angular momentum vector (e.g. "orbit normal")
yBasis = Cartesian3.cross(zBasis, inertialDeltaCartesian, updateTransformCartesian3Scratch3);
if (!Cartesian3.equalsEpsilon(yBasis, Cartesian3.ZERO, CesiumMath.EPSILON7)) {
// X is along the cross of y and z (right handed basis / in the direction of motion)
xBasis = Cartesian3.cross(yBasis, zBasis, updateTransformCartesian3Scratch1);
Matrix3.multiplyByVector(toFixed, xBasis, xBasis);
Matrix3.multiplyByVector(toFixed, yBasis, yBasis);
Matrix3.multiplyByVector(toFixed, zBasis, zBasis);
Cartesian3.normalize(xBasis, xBasis);
Cartesian3.normalize(yBasis, yBasis);
Cartesian3.normalize(zBasis, zBasis);
hasBasis = true;
}
}
}
}
if (defined(that.boundingSphere)) {
cartesian = that.boundingSphere.center;
}
var position;
var direction;
var up;
if (saveCamera) {
position = Cartesian3.clone(camera.position, updateTransformCartesian3Scratch4);
direction = Cartesian3.clone(camera.direction, updateTransformCartesian3Scratch5);
up = Cartesian3.clone(camera.up, updateTransformCartesian3Scratch6);
}
var transform = updateTransformMatrix4Scratch;
if (hasBasis) {
transform[0] = xBasis.x;
transform[1] = xBasis.y;
transform[2] = xBasis.z;
transform[3] = 0.0;
transform[4] = yBasis.x;
transform[5] = yBasis.y;
transform[6] = yBasis.z;
transform[7] = 0.0;
transform[8] = zBasis.x;
transform[9] = zBasis.y;
transform[10] = zBasis.z;
transform[11] = 0.0;
transform[12] = cartesian.x;
transform[13] = cartesian.y;
transform[14] = cartesian.z;
transform[15] = 0.0;
} else {
// Stationary or slow-moving, low-altitude objects use East-North-Up.
Transforms.eastNorthUpToFixedFrame(cartesian, ellipsoid, transform);
}
camera._setTransform(transform);
if (saveCamera) {
Cartesian3.clone(position, camera.position);
Cartesian3.clone(direction, camera.direction);
Cartesian3.clone(up, camera.up);
Cartesian3.cross(direction, up, camera.right);
}
}
if (updateLookAt) {
var offset = (mode === SceneMode.SCENE2D || Cartesian3.equals(that._offset3D, Cartesian3.ZERO)) ? undefined : that._offset3D;
camera.lookAtTransform(camera.transform, offset);
}
}
/**
* A utility object for tracking an entity with the camera.
* @alias EntityView
* @constructor
*
* @param {Entity} entity The entity to track with the camera.
* @param {Scene} scene The scene to use.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use for orienting the camera.
*/
function EntityView(entity, scene, ellipsoid) {
/**
* The entity to track with the camera.
* @type {Entity}
*/
this.entity = entity;
/**
* The scene in which to track the object.
* @type {Scene}
*/
this.scene = scene;
/**
* The ellipsoid to use for orienting the camera.
* @type {Ellipsoid}
*/
this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
/**
* The bounding sphere of the object.
* @type {BoundingSphere}
*/
this.boundingSphere = undefined;
//Shadow copies of the objects so we can detect changes.
this._lastEntity = undefined;
this._mode = undefined;
this._lastCartesian = new Cartesian3();
this._defaultOffset3D = undefined;
this._offset3D = new Cartesian3();
}
// STATIC properties defined here, not per-instance.
defineProperties(EntityView, {
/**
* Gets or sets a camera offset that will be used to
* initialize subsequent EntityViews.
* @memberof EntityView
* @type {Cartesian3}
*/
defaultOffset3D : {
get : function() {
return this._defaultOffset3D;
},
set : function(vector) {
this._defaultOffset3D = Cartesian3.clone(vector, new Cartesian3());
}
}
});
// Initialize the static property.
EntityView.defaultOffset3D = new Cartesian3(-14000, 3500, 3500);
var scratchHeadingPitchRange = new HeadingPitchRange();
var scratchCartesian = new Cartesian3();
/**
* Should be called each animation frame to update the camera
* to the latest settings.
* @param {JulianDate} time The current animation time.
* @param {BoundingSphere} current bounding sphere of the object.
*
*/
EntityView.prototype.update = function(time, boundingSphere) {
var scene = this.scene;
var entity = this.entity;
var ellipsoid = this.ellipsoid;
if (!defined(time)) {
throw new DeveloperError('time is required.');
}
if (!defined(scene)) {
throw new DeveloperError('EntityView.scene is required.');
}
if (!defined(entity)) {
throw new DeveloperError('EntityView.entity is required.');
}
if (!defined(ellipsoid)) {
throw new DeveloperError('EntityView.ellipsoid is required.');
}
if (!defined(entity.position)) {
throw new DeveloperError('entity.position is required.');
}
var sceneMode = scene.mode;
if (sceneMode === SceneMode.MORPHING) {
return;
}
var positionProperty = entity.position;
var objectChanged = entity !== this._lastEntity;
var sceneModeChanged = sceneMode !== this._mode;
var offset3D = this._offset3D;
var camera = scene.camera;
var updateLookAt = objectChanged || sceneModeChanged;
var saveCamera = true;
if (objectChanged) {
var viewFromProperty = entity.viewFrom;
var hasViewFrom = defined(viewFromProperty);
if (!hasViewFrom && defined(boundingSphere)) {
var controller = scene.screenSpaceCameraController;
controller.minimumZoomDistance = Math.min(controller.minimumZoomDistance, boundingSphere.radius * 0.5);
//The default HPR is not ideal for high altitude objects so
//we scale the pitch as we get further from the earth for a more
//downward view.
scratchHeadingPitchRange.pitch = -CesiumMath.PI_OVER_FOUR;
scratchHeadingPitchRange.range = 0;
var position = positionProperty.getValue(time, scratchCartesian);
if (defined(position)) {
var factor = 2 - 1 / Math.max(1, Cartesian3.magnitude(position) / ellipsoid.maximumRadius);
scratchHeadingPitchRange.pitch *= factor;
}
camera.viewBoundingSphere(boundingSphere, scratchHeadingPitchRange);
this.boundingSphere = boundingSphere;
updateLookAt = false;
saveCamera = false;
} else if (!hasViewFrom || !defined(viewFromProperty.getValue(time, offset3D))) {
Cartesian3.clone(EntityView._defaultOffset3D, offset3D);
}
} else if (!sceneModeChanged && scene.mode !== SceneMode.MORPHING && this._mode !== SceneMode.SCENE2D) {
Cartesian3.clone(camera.position, offset3D);
}
this._lastEntity = entity;
this._mode = scene.mode !== SceneMode.MORPHING ? scene.mode : this._mode;
if (scene.mode !== SceneMode.MORPHING) {
updateTransform(this, camera, updateLookAt, saveCamera, positionProperty, time, ellipsoid);
}
};
return EntityView;
});
/**
@license
topojson - https://github.com/mbostock/topojson
Copyright (c) 2012, Michael Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Michael Bostock may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
!function() {
var topojson = {
version: "1.6.18",
mesh: function(topology) { return object(topology, meshArcs.apply(this, arguments)); },
meshArcs: meshArcs,
merge: function(topology) { return object(topology, mergeArcs.apply(this, arguments)); },
mergeArcs: mergeArcs,
feature: featureOrCollection,
neighbors: neighbors,
presimplify: presimplify
};
function stitchArcs(topology, arcs) {
var stitchedArcs = {},
fragmentByStart = {},
fragmentByEnd = {},
fragments = [],
emptyIndex = -1;
// Stitch empty arcs first, since they may be subsumed by other arcs.
arcs.forEach(function(i, j) {
var arc = topology.arcs[i < 0 ? ~i : i], t;
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
}
});
arcs.forEach(function(i) {
var e = ends(i),
start = e[0],
end = e[1],
f, g;
if (f = fragmentByEnd[start]) {
delete fragmentByEnd[f.end];
f.push(i);
f.end = end;
if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
var fg = g === f ? f : f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[end]) {
delete fragmentByStart[f.start];
f.unshift(i);
f.start = start;
if (g = fragmentByEnd[start]) {
delete fragmentByEnd[g.end];
var gf = g === f ? f : g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else {
f = [i];
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
}
});
function ends(i) {
var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
if (topology.transform) p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
else p1 = arc[arc.length - 1];
return i < 0 ? [p1, p0] : [p0, p1];
}
function flush(fragmentByEnd, fragmentByStart) {
for (var k in fragmentByEnd) {
var f = fragmentByEnd[k];
delete fragmentByStart[f.start];
delete f.start;
delete f.end;
f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
fragments.push(f);
}
}
flush(fragmentByEnd, fragmentByStart);
flush(fragmentByStart, fragmentByEnd);
arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); });
return fragments;
}
function meshArcs(topology, o, filter) {
var arcs = [];
if (arguments.length > 1) {
var geomsByArc = [],
geom;
function arc(i) {
var j = i < 0 ? ~i : i;
(geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});
}
function line(arcs) {
arcs.forEach(arc);
}
function polygon(arcs) {
arcs.forEach(line);
}
function geometry(o) {
if (o.type === "GeometryCollection") o.geometries.forEach(geometry);
else if (o.type in geometryType) geom = o, geometryType[o.type](o.arcs);
}
var geometryType = {
LineString: line,
MultiLineString: polygon,
Polygon: polygon,
MultiPolygon: function(arcs) { arcs.forEach(polygon); }
};
geometry(o);
geomsByArc.forEach(arguments.length < 3
? function(geoms) { arcs.push(geoms[0].i); }
: function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i); });
} else {
for (var i = 0, n = topology.arcs.length; i < n; ++i) arcs.push(i);
}
return {type: "MultiLineString", arcs: stitchArcs(topology, arcs)};
}
function mergeArcs(topology, objects) {
var polygonsByArc = {},
polygons = [],
components = [];
objects.forEach(function(o) {
if (o.type === "Polygon") register(o.arcs);
else if (o.type === "MultiPolygon") o.arcs.forEach(register);
});
function register(polygon) {
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
(polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
});
});
polygons.push(polygon);
}
function exterior(ring) {
return cartesianRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]) > 0; // TODO allow spherical?
}
polygons.forEach(function(polygon) {
if (!polygon._) {
var component = [],
neighbors = [polygon];
polygon._ = 1;
components.push(component);
while (polygon = neighbors.pop()) {
component.push(polygon);
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
if (!polygon._) {
polygon._ = 1;
neighbors.push(polygon);
}
});
});
});
}
}
});
polygons.forEach(function(polygon) {
delete polygon._;
});
return {
type: "MultiPolygon",
arcs: components.map(function(polygons) {
var arcs = [];
// Extract the exterior (unique) arcs.
polygons.forEach(function(polygon) {
polygon.forEach(function(ring) {
ring.forEach(function(arc) {
if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
arcs.push(arc);
}
});
});
});
// Stitch the arcs into one or more rings.
arcs = stitchArcs(topology, arcs);
// If more than one ring is returned,
// at most one of these rings can be the exterior;
// this exterior ring has the same winding order
// as any exterior ring in the original polygons.
if ((n = arcs.length) > 1) {
var sgn = exterior(polygons[0][0]);
for (var i = 0, t; i < n; ++i) {
if (sgn === exterior(arcs[i])) {
t = arcs[0], arcs[0] = arcs[i], arcs[i] = t;
break;
}
}
}
return arcs;
})
};
}
function featureOrCollection(topology, o) {
return o.type === "GeometryCollection" ? {
type: "FeatureCollection",
features: o.geometries.map(function(o) { return feature(topology, o); })
} : feature(topology, o);
}
function feature(topology, o) {
var f = {
type: "Feature",
id: o.id,
properties: o.properties || {},
geometry: object(topology, o)
};
if (o.id == null) delete f.id;
return f;
}
function object(topology, o) {
var absolute = transformAbsolute(topology.transform),
arcs = topology.arcs;
function arc(i, points) {
if (points.length) points.pop();
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length, p; k < n; ++k) {
points.push(p = a[k].slice());
absolute(p, k);
}
if (i < 0) reverse(points, n);
}
function point(p) {
p = p.slice();
absolute(p, 0);
return p;
}
function line(arcs) {
var points = [];
for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
if (points.length < 2) points.push(points[0].slice());
return points;
}
function ring(arcs) {
var points = line(arcs);
while (points.length < 4) points.push(points[0].slice());
return points;
}
function polygon(arcs) {
return arcs.map(ring);
}
function geometry(o) {
var t = o.type;
return t === "GeometryCollection" ? {type: t, geometries: o.geometries.map(geometry)}
: t in geometryType ? {type: t, coordinates: geometryType[t](o)}
: null;
}
var geometryType = {
Point: function(o) { return point(o.coordinates); },
MultiPoint: function(o) { return o.coordinates.map(point); },
LineString: function(o) { return line(o.arcs); },
MultiLineString: function(o) { return o.arcs.map(line); },
Polygon: function(o) { return polygon(o.arcs); },
MultiPolygon: function(o) { return o.arcs.map(polygon); }
};
return geometry(o);
}
function reverse(array, n) {
var t, j = array.length, i = j - n; while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
}
function bisect(a, x) {
var lo = 0, hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (a[mid] < x) lo = mid + 1;
else hi = mid;
}
return lo;
}
function neighbors(objects) {
var indexesByArc = {}, // arc index -> array of object indexes
neighbors = objects.map(function() { return []; });
function line(arcs, i) {
arcs.forEach(function(a) {
if (a < 0) a = ~a;
var o = indexesByArc[a];
if (o) o.push(i);
else indexesByArc[a] = [i];
});
}
function polygon(arcs, i) {
arcs.forEach(function(arc) { line(arc, i); });
}
function geometry(o, i) {
if (o.type === "GeometryCollection") o.geometries.forEach(function(o) { geometry(o, i); });
else if (o.type in geometryType) geometryType[o.type](o.arcs, i);
}
var geometryType = {
LineString: line,
MultiLineString: polygon,
Polygon: polygon,
MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); }
};
objects.forEach(geometry);
for (var i in indexesByArc) {
for (var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
for (var k = j + 1; k < m; ++k) {
var ij = indexes[j], ik = indexes[k], n;
if ((n = neighbors[ij])[i = bisect(n, ik)] !== ik) n.splice(i, 0, ik);
if ((n = neighbors[ik])[i = bisect(n, ij)] !== ij) n.splice(i, 0, ij);
}
}
}
return neighbors;
}
function presimplify(topology, triangleArea) {
var absolute = transformAbsolute(topology.transform),
relative = transformRelative(topology.transform),
heap = minAreaHeap();
if (!triangleArea) triangleArea = cartesianTriangleArea;
topology.arcs.forEach(function(arc) {
var triangles = [],
maxArea = 0,
triangle;
// To store each point’s effective area, we create a new array rather than
// extending the passed-in point to workaround a Chrome/V8 bug (getting
// stuck in smi mode). For midpoints, the initial effective area of
// Infinity will be computed in the next step.
for (var i = 0, n = arc.length, p; i < n; ++i) {
p = arc[i];
absolute(arc[i] = [p[0], p[1], Infinity], i);
}
for (var i = 1, n = arc.length - 1; i < n; ++i) {
triangle = arc.slice(i - 1, i + 2);
triangle[1][2] = triangleArea(triangle);
triangles.push(triangle);
heap.push(triangle);
}
for (var i = 0, n = triangles.length; i < n; ++i) {
triangle = triangles[i];
triangle.previous = triangles[i - 1];
triangle.next = triangles[i + 1];
}
while (triangle = heap.pop()) {
var previous = triangle.previous,
next = triangle.next;
// If the area of the current point is less than that of the previous point
// to be eliminated, use the latter's area instead. This ensures that the
// current point cannot be eliminated without eliminating previously-
// eliminated points.
if (triangle[1][2] < maxArea) triangle[1][2] = maxArea;
else maxArea = triangle[1][2];
if (previous) {
previous.next = next;
previous[2] = triangle[2];
update(previous);
}
if (next) {
next.previous = previous;
next[0] = triangle[0];
update(next);
}
}
arc.forEach(relative);
});
function update(triangle) {
heap.remove(triangle);
triangle[1][2] = triangleArea(triangle);
heap.push(triangle);
}
return topology;
}
function cartesianRingArea(ring) {
var i = -1,
n = ring.length,
a,
b = ring[n - 1],
area = 0;
while (++i < n) {
a = b;
b = ring[i];
area += a[0] * b[1] - a[1] * b[0];
}
return area * .5;
}
function cartesianTriangleArea(triangle) {
var a = triangle[0], b = triangle[1], c = triangle[2];
return Math.abs((a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1]));
}
function compareArea(a, b) {
return a[1][2] - b[1][2];
}
function minAreaHeap() {
var heap = {},
array = [],
size = 0;
heap.push = function(object) {
up(array[object._ = size] = object, size++);
return size;
};
heap.pop = function() {
if (size <= 0) return;
var removed = array[0], object;
if (--size > 0) object = array[size], down(array[object._ = 0] = object, 0);
return removed;
};
heap.remove = function(removed) {
var i = removed._, object;
if (array[i] !== removed) return; // invalid request
if (i !== --size) object = array[size], (compareArea(object, removed) < 0 ? up : down)(array[object._ = i] = object, i);
return i;
};
function up(object, i) {
while (i > 0) {
var j = ((i + 1) >> 1) - 1,
parent = array[j];
if (compareArea(object, parent) >= 0) break;
array[parent._ = i] = parent;
array[object._ = i = j] = object;
}
}
function down(object, i) {
while (true) {
var r = (i + 1) << 1,
l = r - 1,
j = i,
child = array[j];
if (l < size && compareArea(array[l], child) < 0) child = array[j = l];
if (r < size && compareArea(array[r], child) < 0) child = array[j = r];
if (j === i) break;
array[child._ = i] = child;
array[object._ = i = j] = object;
}
}
return heap;
}
function transformAbsolute(transform) {
if (!transform) return noop;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(point, i) {
if (!i) x0 = y0 = 0;
point[0] = (x0 += point[0]) * kx + dx;
point[1] = (y0 += point[1]) * ky + dy;
};
}
function transformRelative(transform) {
if (!transform) return noop;
var x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(point, i) {
if (!i) x0 = y0 = 0;
var x1 = (point[0] - dx) / kx | 0,
y1 = (point[1] - dy) / ky | 0;
point[0] = x1 - x0;
point[1] = y1 - y0;
x0 = x1;
y0 = y1;
};
}
function noop() {}
if (typeof define === "function" && define.amd) define('ThirdParty/topojson',topojson);
else if (typeof module === "object" && module.exports) module.exports = topojson;
else this.topojson = topojson;
}();
/*global define*/
define('DataSources/GeoJsonDataSource',[
'../Core/Cartesian3',
'../Core/Color',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/getFilenameFromUri',
'../Core/loadJson',
'../Core/PinBuilder',
'../Core/PolygonHierarchy',
'../Core/RuntimeError',
'../Scene/HeightReference',
'../Scene/VerticalOrigin',
'../ThirdParty/topojson',
'../ThirdParty/when',
'./BillboardGraphics',
'./CallbackProperty',
'./ColorMaterialProperty',
'./ConstantPositionProperty',
'./ConstantProperty',
'./CorridorGraphics',
'./DataSource',
'./EntityCluster',
'./EntityCollection',
'./PolygonGraphics',
'./PolylineGraphics'
], function(
Cartesian3,
Color,
createGuid,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
getFilenameFromUri,
loadJson,
PinBuilder,
PolygonHierarchy,
RuntimeError,
HeightReference,
VerticalOrigin,
topojson,
when,
BillboardGraphics,
CallbackProperty,
ColorMaterialProperty,
ConstantPositionProperty,
ConstantProperty,
CorridorGraphics,
DataSource,
EntityCluster,
EntityCollection,
PolygonGraphics,
PolylineGraphics) {
'use strict';
function defaultCrsFunction(coordinates) {
return Cartesian3.fromDegrees(coordinates[0], coordinates[1], coordinates[2]);
}
var crsNames = {
'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction,
'EPSG:4326' : defaultCrsFunction,
'urn:ogc:def:crs:EPSG::4326' : defaultCrsFunction
};
var crsLinkHrefs = {};
var crsLinkTypes = {};
var defaultMarkerSize = 48;
var defaultMarkerSymbol;
var defaultMarkerColor = Color.ROYALBLUE;
var defaultStroke = Color.YELLOW;
var defaultStrokeWidth = 2;
var defaultFill = Color.fromBytes(255, 255, 0, 100);
var defaultClampToGround = false;
var defaultStrokeWidthProperty = new ConstantProperty(defaultStrokeWidth);
var defaultStrokeMaterialProperty = new ColorMaterialProperty(defaultStroke);
var defaultFillMaterialProperty = new ColorMaterialProperty(defaultFill);
var sizes = {
small : 24,
medium : 48,
large : 64
};
var simpleStyleIdentifiers = ['title', 'description', //
'marker-size', 'marker-symbol', 'marker-color', 'stroke', //
'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity'];
function defaultDescribe(properties, nameProperty) {
var html = '';
for ( var key in properties) {
if (properties.hasOwnProperty(key)) {
if (key === nameProperty || simpleStyleIdentifiers.indexOf(key) !== -1) {
continue;
}
var value = properties[key];
if (defined(value)) {
if (typeof value === 'object') {
html += '' + key + ' ' + defaultDescribe(value) + ' ';
} else {
html += '' + key + ' ' + value + ' ';
}
}
}
}
if (html.length > 0) {
html = '';
}
return html;
}
function createDescriptionCallback(describe, properties, nameProperty) {
var description;
return function(time, result) {
if (!defined(description)) {
description = describe(properties, nameProperty);
}
return description;
};
}
function defaultDescribeProperty(properties, nameProperty) {
return new CallbackProperty(createDescriptionCallback(defaultDescribe, properties, nameProperty), true);
}
//GeoJSON specifies only the Feature object has a usable id property
//But since "multi" geometries create multiple entity,
//we can't use it for them either.
function createObject(geoJson, entityCollection, describe) {
var id = geoJson.id;
if (!defined(id) || geoJson.type !== 'Feature') {
id = createGuid();
} else {
var i = 2;
var finalId = id;
while (defined(entityCollection.getById(finalId))) {
finalId = id + "_" + i;
i++;
}
id = finalId;
}
var entity = entityCollection.getOrCreateEntity(id);
var properties = geoJson.properties;
if (defined(properties)) {
entity.addProperty('properties');
entity.properties = properties;
var nameProperty;
//Check for the simplestyle specified name first.
var name = properties.title;
if (defined(name)) {
entity.name = name;
nameProperty = 'title';
} else {
//Else, find the name by selecting an appropriate property.
//The name will be obtained based on this order:
//1) The first case-insensitive property with the name 'title',
//2) The first case-insensitive property with the name 'name',
//3) The first property containing the word 'title'.
//4) The first property containing the word 'name',
var namePropertyPrecedence = Number.MAX_VALUE;
for ( var key in properties) {
if (properties.hasOwnProperty(key) && properties[key]) {
var lowerKey = key.toLowerCase();
if (namePropertyPrecedence > 1 && lowerKey === 'title') {
namePropertyPrecedence = 1;
nameProperty = key;
break;
} else if (namePropertyPrecedence > 2 && lowerKey === 'name') {
namePropertyPrecedence = 2;
nameProperty = key;
} else if (namePropertyPrecedence > 3 && /title/i.test(key)) {
namePropertyPrecedence = 3;
nameProperty = key;
} else if (namePropertyPrecedence > 4 && /name/i.test(key)) {
namePropertyPrecedence = 4;
nameProperty = key;
}
}
}
if (defined(nameProperty)) {
entity.name = properties[nameProperty];
}
}
var description = properties.description;
if (description !== null) {
entity.description = !defined(description) ? describe(properties, nameProperty) : new ConstantProperty(description);
}
}
return entity;
}
function coordinatesArrayToCartesianArray(coordinates, crsFunction) {
var positions = new Array(coordinates.length);
for (var i = 0; i < coordinates.length; i++) {
positions[i] = crsFunction(coordinates[i]);
}
return positions;
}
// GeoJSON processing functions
function processFeature(dataSource, feature, notUsed, crsFunction, options) {
if (feature.geometry === null) {
//Null geometry is allowed, so just create an empty entity instance for it.
createObject(feature, dataSource._entityCollection, options.describe);
return;
}
if (!defined(feature.geometry)) {
throw new RuntimeError('feature.geometry is required.');
}
var geometryType = feature.geometry.type;
var geometryHandler = geometryTypes[geometryType];
if (!defined(geometryHandler)) {
throw new RuntimeError('Unknown geometry type: ' + geometryType);
}
geometryHandler(dataSource, feature, feature.geometry, crsFunction, options);
}
function processFeatureCollection(dataSource, featureCollection, notUsed, crsFunction, options) {
var features = featureCollection.features;
for (var i = 0, len = features.length; i < len; i++) {
processFeature(dataSource, features[i], undefined, crsFunction, options);
}
}
function processGeometryCollection(dataSource, geoJson, geometryCollection, crsFunction, options) {
var geometries = geometryCollection.geometries;
for (var i = 0, len = geometries.length; i < len; i++) {
var geometry = geometries[i];
var geometryType = geometry.type;
var geometryHandler = geometryTypes[geometryType];
if (!defined(geometryHandler)) {
throw new RuntimeError('Unknown geometry type: ' + geometryType);
}
geometryHandler(dataSource, geoJson, geometry, crsFunction, options);
}
}
function createPoint(dataSource, geoJson, crsFunction, coordinates, options) {
var symbol = options.markerSymbol;
var color = options.markerColor;
var size = options.markerSize;
var properties = geoJson.properties;
if (defined(properties)) {
var cssColor = properties['marker-color'];
if (defined(cssColor)) {
color = Color.fromCssColorString(cssColor);
}
size = defaultValue(sizes[properties['marker-size']], size);
var markerSymbol = properties['marker-symbol'];
if (defined(markerSymbol)) {
symbol = markerSymbol;
}
}
var canvasOrPromise;
if (defined(symbol)) {
if (symbol.length === 1) {
canvasOrPromise = dataSource._pinBuilder.fromText(symbol.toUpperCase(), color, size);
} else {
canvasOrPromise = dataSource._pinBuilder.fromMakiIconId(symbol, color, size);
}
} else {
canvasOrPromise = dataSource._pinBuilder.fromColor(color, size);
}
var billboard = new BillboardGraphics();
billboard.verticalOrigin = new ConstantProperty(VerticalOrigin.BOTTOM);
// Clamp to ground if there isn't a height specified
if (coordinates.length === 2 && options.clampToGround) {
billboard.heightReference = HeightReference.CLAMP_TO_GROUND;
}
var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
entity.billboard = billboard;
entity.position = new ConstantPositionProperty(crsFunction(coordinates));
var promise = when(canvasOrPromise).then(function(image) {
billboard.image = new ConstantProperty(image);
}).otherwise(function() {
billboard.image = new ConstantProperty(dataSource._pinBuilder.fromColor(color, size));
});
dataSource._promises.push(promise);
}
function processPoint(dataSource, geoJson, geometry, crsFunction, options) {
createPoint(dataSource, geoJson, crsFunction, geometry.coordinates, options);
}
function processMultiPoint(dataSource, geoJson, geometry, crsFunction, options) {
var coordinates = geometry.coordinates;
for (var i = 0; i < coordinates.length; i++) {
createPoint(dataSource, geoJson, crsFunction, coordinates[i], options);
}
}
function createLineString(dataSource, geoJson, crsFunction, coordinates, options) {
var material = options.strokeMaterialProperty;
var widthProperty = options.strokeWidthProperty;
var properties = geoJson.properties;
if (defined(properties)) {
var width = properties['stroke-width'];
if (defined(width)) {
widthProperty = new ConstantProperty(width);
}
var color;
var stroke = properties.stroke;
if (defined(stroke)) {
color = Color.fromCssColorString(stroke);
}
var opacity = properties['stroke-opacity'];
if (defined(opacity) && opacity !== 1.0) {
if (!defined(color)) {
color = material.color.clone();
}
color.alpha = opacity;
}
if (defined(color)) {
material = new ColorMaterialProperty(color);
}
}
var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
var graphics;
if (options.clampToGround) {
graphics = new CorridorGraphics();
entity.corridor = graphics;
} else {
graphics = new PolylineGraphics();
entity.polyline = graphics;
}
graphics.material = material;
graphics.width = widthProperty;
graphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction));
}
function processLineString(dataSource, geoJson, geometry, crsFunction, options) {
createLineString(dataSource, geoJson, crsFunction, geometry.coordinates, options);
}
function processMultiLineString(dataSource, geoJson, geometry, crsFunction, options) {
var lineStrings = geometry.coordinates;
for (var i = 0; i < lineStrings.length; i++) {
createLineString(dataSource, geoJson, crsFunction, lineStrings[i], options);
}
}
function createPolygon(dataSource, geoJson, crsFunction, coordinates, options) {
if (coordinates.length === 0 || coordinates[0].length === 0) {
return;
}
var outlineColorProperty = options.strokeMaterialProperty.color;
var material = options.fillMaterialProperty;
var widthProperty = options.strokeWidthProperty;
var properties = geoJson.properties;
if (defined(properties)) {
var width = properties['stroke-width'];
if (defined(width)) {
widthProperty = new ConstantProperty(width);
}
var color;
var stroke = properties.stroke;
if (defined(stroke)) {
color = Color.fromCssColorString(stroke);
}
var opacity = properties['stroke-opacity'];
if (defined(opacity) && opacity !== 1.0) {
if (!defined(color)) {
color = options.strokeMaterialProperty.color.clone();
}
color.alpha = opacity;
}
if (defined(color)) {
outlineColorProperty = new ConstantProperty(color);
}
var fillColor;
var fill = properties.fill;
if (defined(fill)) {
fillColor = Color.fromCssColorString(fill);
fillColor.alpha = material.color.alpha;
}
opacity = properties['fill-opacity'];
if (defined(opacity) && opacity !== material.color.alpha) {
if (!defined(fillColor)) {
fillColor = material.color.clone();
}
fillColor.alpha = opacity;
}
if (defined(fillColor)) {
material = new ColorMaterialProperty(fillColor);
}
}
var polygon = new PolygonGraphics();
polygon.outline = new ConstantProperty(true);
polygon.outlineColor = outlineColorProperty;
polygon.outlineWidth = widthProperty;
polygon.material = material;
var holes = [];
for (var i = 1, len = coordinates.length; i < len; i++) {
holes.push(new PolygonHierarchy(coordinatesArrayToCartesianArray(coordinates[i], crsFunction)));
}
var positions = coordinates[0];
polygon.hierarchy = new ConstantProperty(new PolygonHierarchy(coordinatesArrayToCartesianArray(positions, crsFunction), holes));
if (positions[0].length > 2) {
polygon.perPositionHeight = new ConstantProperty(true);
} else if (!options.clampToGround) {
polygon.height = 0;
}
var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
entity.polygon = polygon;
}
function processPolygon(dataSource, geoJson, geometry, crsFunction, options) {
createPolygon(dataSource, geoJson, crsFunction, geometry.coordinates, options);
}
function processMultiPolygon(dataSource, geoJson, geometry, crsFunction, options) {
var polygons = geometry.coordinates;
for (var i = 0; i < polygons.length; i++) {
createPolygon(dataSource, geoJson, crsFunction, polygons[i], options);
}
}
function processTopology(dataSource, geoJson, geometry, crsFunction, options) {
for ( var property in geometry.objects) {
if (geometry.objects.hasOwnProperty(property)) {
var feature = topojson.feature(geometry, geometry.objects[property]);
var typeHandler = geoJsonObjectTypes[feature.type];
typeHandler(dataSource, feature, feature, crsFunction, options);
}
}
}
var geoJsonObjectTypes = {
Feature : processFeature,
FeatureCollection : processFeatureCollection,
GeometryCollection : processGeometryCollection,
LineString : processLineString,
MultiLineString : processMultiLineString,
MultiPoint : processMultiPoint,
MultiPolygon : processMultiPolygon,
Point : processPoint,
Polygon : processPolygon,
Topology : processTopology
};
var geometryTypes = {
GeometryCollection : processGeometryCollection,
LineString : processLineString,
MultiLineString : processMultiLineString,
MultiPoint : processMultiPoint,
MultiPolygon : processMultiPolygon,
Point : processPoint,
Polygon : processPolygon,
Topology : processTopology
};
/**
* A {@link DataSource} which processes both
* {@link http://www.geojson.org/|GeoJSON} and {@link https://github.com/mbostock/topojson|TopoJSON} data.
* {@link https://github.com/mapbox/simplestyle-spec|simplestyle-spec} properties will also be used if they
* are present.
*
* @alias GeoJsonDataSource
* @constructor
*
* @param {String} [name] The name of this data source. If undefined, a name will be taken from
* the name of the GeoJSON file.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20and%20TopoJSON.html|Cesium Sandcastle GeoJSON and TopoJSON Demo}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20simplestyle.html|Cesium Sandcastle GeoJSON simplestyle Demo}
*
* @example
* var viewer = new Cesium.Viewer('cesiumContainer');
* viewer.dataSources.add(Cesium.GeoJsonDataSource.load('../../SampleData/ne_10m_us_states.topojson', {
* stroke: Cesium.Color.HOTPINK,
* fill: Cesium.Color.PINK,
* strokeWidth: 3,
* markerSymbol: '?'
* }));
*/
function GeoJsonDataSource(name) {
this._name = name;
this._changed = new Event();
this._error = new Event();
this._isLoading = false;
this._loading = new Event();
this._entityCollection = new EntityCollection(this);
this._promises = [];
this._pinBuilder = new PinBuilder();
this._entityCluster = new EntityCluster();
}
/**
* Creates a Promise to a new instance loaded with the provided GeoJSON or TopoJSON data.
*
* @param {String|Object} data A url, GeoJSON object, or TopoJSON object to be loaded.
* @param {Object} [options] An object with the following properties:
* @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
* @param {Number} [options.markerSize=GeoJsonDataSource.markerSize] The default size of the map pin created for each point, in pixels.
* @param {String} [options.markerSymbol=GeoJsonDataSource.markerSymbol] The default symbol of the map pin created for each point.
* @param {Color} [options.markerColor=GeoJsonDataSource.markerColor] The default color of the map pin created for each point.
* @param {Color} [options.stroke=GeoJsonDataSource.stroke] The default color of polylines and polygon outlines.
* @param {Number} [options.strokeWidth=GeoJsonDataSource.strokeWidth] The default width of polylines and polygon outlines.
* @param {Color} [options.fill=GeoJsonDataSource.fill] The default color for polygon interiors.
* @param {Boolean} [options.clampToGround=GeoJsonDataSource.clampToGround] true if we want the geometry features (polygons or linestrings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
*
* @returns {Promise.} A promise that will resolve when the data is loaded.
*/
GeoJsonDataSource.load = function(data, options) {
return new GeoJsonDataSource().load(data, options);
};
defineProperties(GeoJsonDataSource, {
/**
* Gets or sets the default size of the map pin created for each point, in pixels.
* @memberof GeoJsonDataSource
* @type {Number}
* @default 48
*/
markerSize : {
get : function() {
return defaultMarkerSize;
},
set : function(value) {
defaultMarkerSize = value;
}
},
/**
* Gets or sets the default symbol of the map pin created for each point.
* This can be any valid {@link http://mapbox.com/maki/|Maki} identifier, any single character,
* or blank if no symbol is to be used.
* @memberof GeoJsonDataSource
* @type {String}
*/
markerSymbol : {
get : function() {
return defaultMarkerSymbol;
},
set : function(value) {
defaultMarkerSymbol = value;
}
},
/**
* Gets or sets the default color of the map pin created for each point.
* @memberof GeoJsonDataSource
* @type {Color}
* @default Color.ROYALBLUE
*/
markerColor : {
get : function() {
return defaultMarkerColor;
},
set : function(value) {
defaultMarkerColor = value;
}
},
/**
* Gets or sets the default color of polylines and polygon outlines.
* @memberof GeoJsonDataSource
* @type {Color}
* @default Color.BLACK
*/
stroke : {
get : function() {
return defaultStroke;
},
set : function(value) {
defaultStroke = value;
defaultStrokeMaterialProperty.color.setValue(value);
}
},
/**
* Gets or sets the default width of polylines and polygon outlines.
* @memberof GeoJsonDataSource
* @type {Number}
* @default 2.0
*/
strokeWidth : {
get : function() {
return defaultStrokeWidth;
},
set : function(value) {
defaultStrokeWidth = value;
defaultStrokeWidthProperty.setValue(value);
}
},
/**
* Gets or sets default color for polygon interiors.
* @memberof GeoJsonDataSource
* @type {Color}
* @default Color.YELLOW
*/
fill : {
get : function() {
return defaultFill;
},
set : function(value) {
defaultFill = value;
defaultFillMaterialProperty = new ColorMaterialProperty(defaultFill);
}
},
/**
* Gets or sets default of whether to clamp to the ground.
* @memberof GeoJsonDataSource
* @type {Boolean}
* @default false
*/
clampToGround : {
get : function() {
return defaultClampToGround;
},
set : function(value) {
defaultClampToGround = value;
}
},
/**
* Gets an object that maps the name of a crs to a callback function which takes a GeoJSON coordinate
* and transforms it into a WGS84 Earth-fixed Cartesian. Older versions of GeoJSON which
* supported the EPSG type can be added to this list as well, by specifying the complete EPSG name,
* for example 'EPSG:4326'.
* @memberof GeoJsonDataSource
* @type {Object}
*/
crsNames : {
get : function() {
return crsNames;
}
},
/**
* Gets an object that maps the href property of a crs link to a callback function
* which takes the crs properties object and returns a Promise that resolves
* to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian.
* Items in this object take precedence over those defined in crsLinkHrefs
, assuming
* the link has a type specified.
* @memberof GeoJsonDataSource
* @type {Object}
*/
crsLinkHrefs : {
get : function() {
return crsLinkHrefs;
}
},
/**
* Gets an object that maps the type property of a crs link to a callback function
* which takes the crs properties object and returns a Promise that resolves
* to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian.
* Items in crsLinkHrefs
take precedence over this object.
* @memberof GeoJsonDataSource
* @type {Object}
*/
crsLinkTypes : {
get : function() {
return crsLinkTypes;
}
}
});
defineProperties(GeoJsonDataSource.prototype, {
/**
* Gets a human-readable name for this instance.
* @memberof GeoJsonDataSource.prototype
* @type {String}
*/
name : {
get : function() {
return this._name;
}
},
/**
* This DataSource only defines static data, therefore this property is always undefined.
* @memberof GeoJsonDataSource.prototype
* @type {DataSourceClock}
*/
clock : {
value : undefined,
writable : false
},
/**
* Gets the collection of {@link Entity} instances.
* @memberof GeoJsonDataSource.prototype
* @type {EntityCollection}
*/
entities : {
get : function() {
return this._entityCollection;
}
},
/**
* Gets a value indicating if the data source is currently loading data.
* @memberof GeoJsonDataSource.prototype
* @type {Boolean}
*/
isLoading : {
get : function() {
return this._isLoading;
}
},
/**
* Gets an event that will be raised when the underlying data changes.
* @memberof GeoJsonDataSource.prototype
* @type {Event}
*/
changedEvent : {
get : function() {
return this._changed;
}
},
/**
* Gets an event that will be raised if an error is encountered during processing.
* @memberof GeoJsonDataSource.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._error;
}
},
/**
* Gets an event that will be raised when the data source either starts or stops loading.
* @memberof GeoJsonDataSource.prototype
* @type {Event}
*/
loadingEvent : {
get : function() {
return this._loading;
}
},
/**
* Gets whether or not this data source should be displayed.
* @memberof GeoJsonDataSource.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._entityCollection.show;
},
set : function(value) {
this._entityCollection.show = value;
}
},
/**
* Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
*
* @memberof GeoJsonDataSource.prototype
* @type {EntityCluster}
*/
clustering : {
get : function() {
return this._entityCluster;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value must be defined.');
}
this._entityCluster = value;
}
}
});
/**
* Asynchronously loads the provided GeoJSON or TopoJSON data, replacing any existing data.
*
* @param {String|Object} data A url, GeoJSON object, or TopoJSON object to be loaded.
* @param {Object} [options] An object with the following properties:
* @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
* @param {GeoJsonDataSource~describe} [options.describe=GeoJsonDataSource.defaultDescribeProperty] A function which returns a Property object (or just a string),
* which converts the properties into an html description.
* @param {Number} [options.markerSize=GeoJsonDataSource.markerSize] The default size of the map pin created for each point, in pixels.
* @param {String} [options.markerSymbol=GeoJsonDataSource.markerSymbol] The default symbol of the map pin created for each point.
* @param {Color} [options.markerColor=GeoJsonDataSource.markerColor] The default color of the map pin created for each point.
* @param {Color} [options.stroke=GeoJsonDataSource.stroke] The default color of polylines and polygon outlines.
* @param {Number} [options.strokeWidth=GeoJsonDataSource.strokeWidth] The default width of polylines and polygon outlines.
* @param {Color} [options.fill=GeoJsonDataSource.fill] The default color for polygon interiors.
* @param {Boolean} [options.clampToGround=GeoJsonDataSource.clampToGround] true if we want the features clamped to the ground.
*
* @returns {Promise.} a promise that will resolve when the GeoJSON is loaded.
*/
GeoJsonDataSource.prototype.load = function(data, options) {
if (!defined(data)) {
throw new DeveloperError('data is required.');
}
DataSource.setLoading(this, true);
var promise = data;
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var sourceUri = options.sourceUri;
if (typeof data === 'string') {
if (!defined(sourceUri)) {
sourceUri = data;
}
promise = loadJson(data);
}
options = {
describe: defaultValue(options.describe, defaultDescribeProperty),
markerSize : defaultValue(options.markerSize, defaultMarkerSize),
markerSymbol : defaultValue(options.markerSymbol, defaultMarkerSymbol),
markerColor : defaultValue(options.markerColor, defaultMarkerColor),
strokeWidthProperty : new ConstantProperty(defaultValue(options.strokeWidth, defaultStrokeWidth)),
strokeMaterialProperty : new ColorMaterialProperty(defaultValue(options.stroke, defaultStroke)),
fillMaterialProperty : new ColorMaterialProperty(defaultValue(options.fill, defaultFill)),
clampToGround : defaultValue(options.clampToGround, defaultClampToGround)
};
var that = this;
return when(promise, function(geoJson) {
return load(that, geoJson, options, sourceUri);
}).otherwise(function(error) {
DataSource.setLoading(that, false);
that._error.raiseEvent(that, error);
console.log(error);
return when.reject(error);
});
};
function load(that, geoJson, options, sourceUri) {
var name;
if (defined(sourceUri)) {
name = getFilenameFromUri(sourceUri);
}
if (defined(name) && that._name !== name) {
that._name = name;
that._changed.raiseEvent(that);
}
var typeHandler = geoJsonObjectTypes[geoJson.type];
if (!defined(typeHandler)) {
throw new RuntimeError('Unsupported GeoJSON object type: ' + geoJson.type);
}
//Check for a Coordinate Reference System.
var crs = geoJson.crs;
var crsFunction = crs !== null ? defaultCrsFunction : null;
if (defined(crs)) {
if (!defined(crs.properties)) {
throw new RuntimeError('crs.properties is undefined.');
}
var properties = crs.properties;
if (crs.type === 'name') {
crsFunction = crsNames[properties.name];
if (!defined(crsFunction)) {
throw new RuntimeError('Unknown crs name: ' + properties.name);
}
} else if (crs.type === 'link') {
var handler = crsLinkHrefs[properties.href];
if (!defined(handler)) {
handler = crsLinkTypes[properties.type];
}
if (!defined(handler)) {
throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties));
}
crsFunction = handler(properties);
} else if (crs.type === 'EPSG') {
crsFunction = crsNames['EPSG:' + properties.code];
if (!defined(crsFunction)) {
throw new RuntimeError('Unknown crs EPSG code: ' + properties.code);
}
} else {
throw new RuntimeError('Unknown crs type: ' + crs.type);
}
}
return when(crsFunction, function(crsFunction) {
that._entityCollection.removeAll();
// null is a valid value for the crs, but means the entire load process becomes a no-op
// because we can't assume anything about the coordinates.
if (crsFunction !== null) {
typeHandler(that, geoJson, geoJson, crsFunction, options);
}
return when.all(that._promises, function() {
that._promises.length = 0;
DataSource.setLoading(that, false);
return that;
});
});
}
/**
* This callback is displayed as part of the GeoJsonDataSource class.
* @callback GeoJsonDataSource~describe
* @param {Object} properties The properties of the feature.
* @param {String} nameProperty The property key that Cesium estimates to have the name of the feature.
*/
return GeoJsonDataSource;
});
/*global define*/
define('DataSources/GeometryUpdater',[
'../Core/defineProperties',
'../Core/DeveloperError'
], function(
defineProperties,
DeveloperError) {
'use strict';
/**
* Defines the interface for a geometry updater. A GeometryUpdater maps
* geometry defined as part of a {@link Entity} into {@link Geometry}
* instances. These instances are then visualized by {@link GeometryVisualizer}.
*
* This type defines an interface and cannot be instantiated directly.
*
* @alias GeometryUpdater
* @constructor
*
* @param {Entity} entity The entity containing the geometry to be visualized.
* @param {Scene} scene The scene where visualization is taking place.
*
* @see EllipseGeometryUpdater
* @see EllipsoidGeometryUpdater
* @see PolygonGeometryUpdater
* @see PolylineGeometryUpdater
* @see RectangleGeometryUpdater
* @see WallGeometryUpdater
*/
function GeometryUpdater(entity, scene) {
DeveloperError.throwInstantiationError();
}
defineProperties(GeometryUpdater, {
/**
* Gets the type of Appearance to use for simple color-based geometry.
* @memberof GeometryUpdater
* @type {Appearance}
*/
perInstanceColorAppearanceType : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the type of Appearance to use for material-based geometry.
* @memberof GeometryUpdater
* @type {Appearance}
*/
materialAppearanceType : {
get : DeveloperError.throwInstantiationError
}
});
defineProperties(GeometryUpdater.prototype, {
/**
* Gets the entity associated with this geometry.
* @memberof GeometryUpdater.prototype
*
* @type {Entity}
* @readonly
*/
entity : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if the geometry has a fill component.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
fillEnabled : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if fill visibility varies with simulation time.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantFill : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the material property used to fill the geometry.
* @memberof GeometryUpdater.prototype
*
* @type {MaterialProperty}
* @readonly
*/
fillMaterialProperty : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if the geometry has an outline component.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
outlineEnabled : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if outline visibility varies with simulation time.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
hasConstantOutline : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the {@link Color} property for the geometry outline.
* @memberof GeometryUpdater.prototype
*
* @type {Property}
* @readonly
*/
outlineColorProperty : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the constant with of the geometry outline, in pixels.
* This value is only valid if isDynamic is false.
* @memberof GeometryUpdater.prototype
*
* @type {Number}
* @readonly
*/
outlineWidth : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if the geometry is time-varying.
* If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
* returned by GeometryUpdater#createDynamicUpdater.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isDynamic : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating if the geometry is closed.
* This property is only valid for static geometry.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
isClosed : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets an event that is raised whenever the public properties
* of this updater change.
* @memberof GeometryUpdater.prototype
*
* @type {Boolean}
* @readonly
*/
geometryChanged : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Checks if the geometry is outlined at the provided time.
* @function
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
*/
GeometryUpdater.prototype.isOutlineVisible = DeveloperError.throwInstantiationError;
/**
* Checks if the geometry is filled at the provided time.
* @function
*
* @param {JulianDate} time The time for which to retrieve visibility.
* @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
*/
GeometryUpdater.prototype.isFilled = DeveloperError.throwInstantiationError;
/**
* Creates the geometry instance which represents the fill of the geometry.
* @function
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent a filled geometry.
*/
GeometryUpdater.prototype.createFillGeometryInstance = DeveloperError.throwInstantiationError;
/**
* Creates the geometry instance which represents the outline of the geometry.
* @function
*
* @param {JulianDate} time The time to use when retrieving initial attribute values.
* @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
*
* @exception {DeveloperError} This instance does not represent an outlined geometry.
*/
GeometryUpdater.prototype.createOutlineGeometryInstance = DeveloperError.throwInstantiationError;
/**
* Returns true if this object was destroyed; otherwise, false.
* @function
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
GeometryUpdater.prototype.isDestroyed = DeveloperError.throwInstantiationError;
/**
* Destroys and resources used by the object. Once an object is destroyed, it should not be used.
* @function
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
GeometryUpdater.prototype.destroy = DeveloperError.throwInstantiationError;
/**
* Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
* @function
*
* @param {PrimitiveCollection} primitives The primitive collection to use.
* @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
*
* @exception {DeveloperError} This instance does not represent dynamic geometry.
*/
GeometryUpdater.prototype.createDynamicUpdater = DeveloperError.throwInstantiationError;
return GeometryUpdater;
});
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define('ThirdParty/Autolinker',[], function () {
return (root['Autolinker'] = factory());
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
root['Autolinker'] = factory();
}
}(this, function () {
/*!
* Autolinker.js
* 0.17.1
*
* Copyright(c) 2015 Gregory Jacobs
* MIT Licensed. http://www.opensource.org/licenses/mit-license.php
*
* https://github.com/gregjacobs/Autolinker.js
*/
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the matches in
* the appropriate anchor (<a>) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object (map) provided
* to the Autolinker constructor, which will configure how the {@link #link link()}
* method will process the links.
*
* For example:
*
* var autolinker = new Autolinker( {
* newWindow : false,
* truncate : 30
* } );
*
* var html = autolinker.link( "Joe went to www.yahoo.com" );
* // produces: 'Joe went to yahoo.com '
*
*
* The {@link #static-link static link()} method may also be used to inline options into a single call, which may
* be more convenient for one-off uses. For example:
*
* var html = Autolinker.link( "Joe went to www.yahoo.com", {
* newWindow : false,
* truncate : 30
* } );
* // produces: 'Joe went to yahoo.com '
*
*
* ## Custom Replacements of Links
*
* If the configuration options do not provide enough flexibility, a {@link #replaceFn}
* may be provided to fully customize the output of Autolinker. This function is
* called once for each URL/Email/Phone#/Twitter Handle/Hashtag match that is
* encountered.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( autolinker, match ) {
* console.log( "href = ", match.getAnchorHref() );
* console.log( "text = ", match.getAnchorText() );
*
* switch( match.getType() ) {
* case 'url' :
* console.log( "url: ", match.getUrl() );
*
* if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
* var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
* tag.setAttr( 'rel', 'nofollow' );
* tag.addClass( 'external-link' );
*
* return tag;
*
* } else {
* return true; // let Autolinker perform its normal anchor tag replacement
* }
*
* case 'email' :
* var email = match.getEmail();
* console.log( "email: ", email );
*
* if( email === "my@own.address" ) {
* return false; // don't auto-link this particular email address; leave as-is
* } else {
* return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
* }
*
* case 'phone' :
* var phoneNumber = match.getPhoneNumber();
* console.log( phoneNumber );
*
* return '' + phoneNumber + ' ';
*
* case 'twitter' :
* var twitterHandle = match.getTwitterHandle();
* console.log( twitterHandle );
*
* return '' + twitterHandle + ' ';
*
* case 'hashtag' :
* var hashtag = match.getHashtag();
* console.log( hashtag );
*
* return '' + hashtag + ' ';
* }
* }
* } );
*
*
* The function may return the following values:
*
* - `true` (Boolean): Allow Autolinker to replace the match as it normally would.
* - `false` (Boolean): Do not replace the current match at all - leave as-is.
* - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for
* the match.
* - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text.
*
* @constructor
* @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map).
*/
var Autolinker = function( cfg ) {
Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs.
// Validate the value of the `hashtag` cfg.
var hashtag = this.hashtag;
if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' ) {
throw new Error( "invalid `hashtag` cfg - see docs" );
}
};
Autolinker.prototype = {
constructor : Autolinker, // fix constructor property
/**
* @cfg {Boolean} urls
*
* `true` if miscellaneous URLs should be automatically linked, `false` if they should not be.
*/
urls : true,
/**
* @cfg {Boolean} email
*
* `true` if email addresses should be automatically linked, `false` if they should not be.
*/
email : true,
/**
* @cfg {Boolean} twitter
*
* `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be.
*/
twitter : true,
/**
* @cfg {Boolean} phone
*
* `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be.
*/
phone: true,
/**
* @cfg {Boolean/String} hashtag
*
* A string for the service name to have hashtags (ex: "#myHashtag")
* auto-linked to. The currently-supported values are:
*
* - 'twitter'
* - 'facebook'
*
* Pass `false` to skip auto-linking of hashtags.
*/
hashtag : false,
/**
* @cfg {Boolean} newWindow
*
* `true` if the links should open in a new window, `false` otherwise.
*/
newWindow : true,
/**
* @cfg {Boolean} stripPrefix
*
* `true` if 'http://' or 'https://' and/or the 'www.' should be stripped
* from the beginning of URL links' text, `false` otherwise.
*/
stripPrefix : true,
/**
* @cfg {Number} truncate
*
* A number for how many characters long matched text should be truncated to inside the text of
* a link. If the matched text is over this number of characters, it will be truncated to this length by
* adding a two period ellipsis ('..') to the end of the string.
*
* For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look
* something like this: 'yahoo.com/some/long/pat..'
*/
truncate : undefined,
/**
* @cfg {String} className
*
* A CSS class name to add to the generated links. This class will be added to all links, as well as this class
* plus match suffixes for styling url/email/phone/twitter/hashtag links differently.
*
* For example, if this config is provided as "myLink", then:
*
* - URL links will have the CSS classes: "myLink myLink-url"
* - Email links will have the CSS classes: "myLink myLink-email", and
* - Twitter links will have the CSS classes: "myLink myLink-twitter"
* - Phone links will have the CSS classes: "myLink myLink-phone"
* - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
*/
className : "",
/**
* @cfg {Function} replaceFn
*
* A function to individually process each match found in the input string.
*
* See the class's description for usage.
*
* This function is called with the following parameters:
*
* @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such
* as the instance's {@link #getTagBuilder tag builder}).
* @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the
* match that the `replaceFn` is currently processing. See {@link Autolinker.match.Match} subclasses for details.
*/
/**
* @private
* @property {Autolinker.htmlParser.HtmlParser} htmlParser
*
* The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated
* in the {@link #getHtmlParser} method.
*/
htmlParser : undefined,
/**
* @private
* @property {Autolinker.matchParser.MatchParser} matchParser
*
* The MatchParser instance used to find matches in the text nodes of an input string passed to
* {@link #link}. This is lazily instantiated in the {@link #getMatchParser} method.
*/
matchParser : undefined,
/**
* @private
* @property {Autolinker.AnchorTagBuilder} tagBuilder
*
* The AnchorTagBuilder instance used to build match replacement anchor tags. Note: this is lazily instantiated
* in the {@link #getTagBuilder} method.
*/
tagBuilder : undefined,
/**
* Automatically links URLs, Email addresses, Phone numbers, Twitter
* handles, and Hashtags found in the given chunk of HTML. Does not link
* URLs found within HTML tags.
*
* For instance, if given the text: `You should go to http://www.yahoo.com`,
* then the result will be `You should go to
* <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
*
* This method finds the text around any HTML elements in the input
* `textOrHtml`, which will be the text that is processed. Any original HTML
* elements will be left as-is, as well as the text that is already wrapped
* in anchor (<a>) tags.
*
* @param {String} textOrHtml The HTML or text to autolink matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@link #twitter}, and {@link #hashtag} options are enabled).
* @return {String} The HTML, with matches automatically linked.
*/
link : function( textOrHtml ) {
var htmlParser = this.getHtmlParser(),
htmlNodes = htmlParser.parse( textOrHtml ),
anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have
resultHtml = [];
for( var i = 0, len = htmlNodes.length; i < len; i++ ) {
var node = htmlNodes[ i ],
nodeType = node.getType(),
nodeText = node.getText();
if( nodeType === 'element' ) {
// Process HTML nodes in the input `textOrHtml`
if( node.getTagName() === 'a' ) {
if( !node.isClosing() ) { // it's the start tag
anchorTagStackCount++;
} else { // it's the end tag
anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0
}
}
resultHtml.push( nodeText ); // now add the text of the tag itself verbatim
} else if( nodeType === 'entity' || nodeType === 'comment' ) {
resultHtml.push( nodeText ); // append HTML entity nodes (such as ' ') or HTML comments (such as '') verbatim
} else {
// Process text nodes in the input `textOrHtml`
if( anchorTagStackCount === 0 ) {
// If we're not within an tag, process the text node to linkify
var linkifiedStr = this.linkifyStr( nodeText );
resultHtml.push( linkifiedStr );
} else {
// `text` is within an tag, simply append the text - we do not want to autolink anything
// already within an ... tag
resultHtml.push( nodeText );
}
}
}
return resultHtml.join( "" );
},
/**
* Process the text that lies in between HTML tags, performing the anchor
* tag replacements for the matches, and returns the string with the
* replacements made.
*
* This method does the actual wrapping of matches with anchor tags.
*
* @private
* @param {String} str The string of text to auto-link.
* @return {String} The text with anchor tags auto-filled.
*/
linkifyStr : function( str ) {
return this.getMatchParser().replace( str, this.createMatchReturnVal, this );
},
/**
* Creates the return string value for a given match in the input string,
* for the {@link #linkifyStr} method.
*
* This method handles the {@link #replaceFn}, if one was provided.
*
* @private
* @param {Autolinker.match.Match} match The Match object that represents the match.
* @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but
* may be the `matchStr` itself if the match is not to be replaced.
*/
createMatchReturnVal : function( match ) {
// Handle a custom `replaceFn` being provided
var replaceFnResult;
if( this.replaceFn ) {
replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg
}
if( typeof replaceFnResult === 'string' ) {
return replaceFnResult; // `replaceFn` returned a string, use that
} else if( replaceFnResult === false ) {
return match.getMatchedText(); // no replacement for the match
} else if( replaceFnResult instanceof Autolinker.HtmlTag ) {
return replaceFnResult.toAnchorString();
} else { // replaceFnResult === true, or no/unknown return value from function
// Perform Autolinker's default anchor tag generation
var tagBuilder = this.getTagBuilder(),
anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance
return anchorTag.toAnchorString();
}
},
/**
* Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance.
*
* @protected
* @return {Autolinker.htmlParser.HtmlParser}
*/
getHtmlParser : function() {
var htmlParser = this.htmlParser;
if( !htmlParser ) {
htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser();
}
return htmlParser;
},
/**
* Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance.
*
* @protected
* @return {Autolinker.matchParser.MatchParser}
*/
getMatchParser : function() {
var matchParser = this.matchParser;
if( !matchParser ) {
matchParser = this.matchParser = new Autolinker.matchParser.MatchParser( {
urls : this.urls,
email : this.email,
twitter : this.twitter,
phone : this.phone,
hashtag : this.hashtag,
stripPrefix : this.stripPrefix
} );
}
return matchParser;
},
/**
* Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it
* if it does not yet exist.
*
* This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that
* Autolinker would normally generate, and then allow for modifications before returning it. For example:
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance
* tag.setAttr( 'rel', 'nofollow' );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test google.com
*
* @return {Autolinker.AnchorTagBuilder}
*/
getTagBuilder : function() {
var tagBuilder = this.tagBuilder;
if( !tagBuilder ) {
tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( {
newWindow : this.newWindow,
truncate : this.truncate,
className : this.className
} );
}
return tagBuilder;
}
};
/**
* Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
* and Hashtags found in the given chunk of HTML. Does not link URLs found
* within HTML tags.
*
* For instance, if given the text: `You should go to http://www.yahoo.com`,
* then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
*
* Example:
*
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
* // Produces: "Go to google.com "
*
* @static
* @param {String} textOrHtml The HTML or text to find matches within (depending
* on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter},
* and {@link #hashtag} options are enabled).
* @param {Object} [options] Any of the configuration options for the Autolinker
* class, specified in an Object (map). See the class description for an
* example call.
* @return {String} The HTML text, with matches automatically linked.
*/
Autolinker.link = function( textOrHtml, options ) {
var autolinker = new Autolinker( options );
return autolinker.link( textOrHtml );
};
// Autolinker Namespaces
Autolinker.match = {};
Autolinker.htmlParser = {};
Autolinker.matchParser = {};
/*global Autolinker */
/*jshint eqnull:true, boss:true */
/**
* @class Autolinker.Util
* @singleton
*
* A few utility methods for Autolinker.
*/
Autolinker.Util = {
/**
* @property {Function} abstractMethod
*
* A function object which represents an abstract method.
*/
abstractMethod : function() { throw "abstract"; },
/**
* @private
* @property {RegExp} trimRegex
*
* The regular expression used to trim the leading and trailing whitespace
* from a string.
*/
trimRegex : /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
/**
* Assigns (shallow copies) the properties of `src` onto `dest`.
*
* @param {Object} dest The destination object.
* @param {Object} src The source object.
* @return {Object} The destination object (`dest`)
*/
assign : function( dest, src ) {
for( var prop in src ) {
if( src.hasOwnProperty( prop ) ) {
dest[ prop ] = src[ prop ];
}
}
return dest;
},
/**
* Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype.
*
* @param {Function} superclass The constructor function for the superclass.
* @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the
* special property `constructor`, which will be used as the new subclass's constructor function.
* @return {Function} The new subclass function.
*/
extend : function( superclass, protoProps ) {
var superclassProto = superclass.prototype;
var F = function() {};
F.prototype = superclassProto;
var subclass;
if( protoProps.hasOwnProperty( 'constructor' ) ) {
subclass = protoProps.constructor;
} else {
subclass = function() { superclassProto.constructor.apply( this, arguments ); };
}
var subclassProto = subclass.prototype = new F(); // set up prototype chain
subclassProto.constructor = subclass; // fix constructor property
subclassProto.superclass = superclassProto;
delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there
Autolinker.Util.assign( subclassProto, protoProps );
return subclass;
},
/**
* Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the
* end of the string (by default, two periods: '..'). If the `str` length does not exceed
* `len`, the string will be returned unchanged.
*
* @param {String} str The string to truncate and add an ellipsis to.
* @param {Number} truncateLen The length to truncate the string at.
* @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str`
* when truncated. Defaults to '..'
*/
ellipsis : function( str, truncateLen, ellipsisChars ) {
if( str.length > truncateLen ) {
ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars;
str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars;
}
return str;
},
/**
* Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below).
*
* @param {Array} arr The array to find an element of.
* @param {*} element The element to find in the array, and return the index of.
* @return {Number} The index of the `element`, or -1 if it was not found.
*/
indexOf : function( arr, element ) {
if( Array.prototype.indexOf ) {
return arr.indexOf( element );
} else {
for( var i = 0, len = arr.length; i < len; i++ ) {
if( arr[ i ] === element ) return i;
}
return -1;
}
},
/**
* Performs the functionality of what modern browsers do when `String.prototype.split()` is called
* with a regular expression that contains capturing parenthesis.
*
* For example:
*
* // Modern browsers:
* "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ]
*
* // Old IE (including IE8):
* "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ]
*
* This method emulates the functionality of modern browsers for the old IE case.
*
* @param {String} str The string to split.
* @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting
* character(s) will be spliced into the array, as in the "modern browsers" example in the
* description of this method.
* Note #1: the supplied regular expression **must** have the 'g' flag specified.
* Note #2: for simplicity's sake, the regular expression does not need
* to contain capturing parenthesis - it will be assumed that any match has them.
* @return {String[]} The split array of strings, with the splitting character(s) included.
*/
splitAndCapture : function( str, splitRegex ) {
if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" );
var result = [],
lastIdx = 0,
match;
while( match = splitRegex.exec( str ) ) {
result.push( str.substring( lastIdx, match.index ) );
result.push( match[ 0 ] ); // push the splitting char(s)
lastIdx = match.index + match[ 0 ].length;
}
result.push( str.substring( lastIdx ) );
return result;
},
/**
* Trims the leading and trailing whitespace from a string.
*
* @param {String} str The string to trim.
* @return {String}
*/
trim : function( str ) {
return str.replace( this.trimRegex, '' );
}
};
/*global Autolinker */
/*jshint boss:true */
/**
* @class Autolinker.HtmlTag
* @extends Object
*
* Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.
*
* Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use
* this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.
*
* ## Examples
*
* Example instantiation:
*
* var tag = new Autolinker.HtmlTag( {
* tagName : 'a',
* attrs : { 'href': 'http://google.com', 'class': 'external-link' },
* innerHtml : 'Google'
* } );
*
* tag.toAnchorString(); // Google
*
* // Individual accessor methods
* tag.getTagName(); // 'a'
* tag.getAttr( 'href' ); // 'http://google.com'
* tag.hasClass( 'external-link' ); // true
*
*
* Using mutator methods (which may be used in combination with instantiation config properties):
*
* var tag = new Autolinker.HtmlTag();
* tag.setTagName( 'a' );
* tag.setAttr( 'href', 'http://google.com' );
* tag.addClass( 'external-link' );
* tag.setInnerHtml( 'Google' );
*
* tag.getTagName(); // 'a'
* tag.getAttr( 'href' ); // 'http://google.com'
* tag.hasClass( 'external-link' ); // true
*
* tag.toAnchorString(); // Google
*
*
* ## Example use within a {@link Autolinker#replaceFn replaceFn}
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text
* tag.setAttr( 'rel', 'nofollow' );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test google.com
*
*
* ## Example use with a new tag for the replacement
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = new Autolinker.HtmlTag( {
* tagName : 'button',
* attrs : { 'title': 'Load URL: ' + match.getAnchorHref() },
* innerHtml : 'Load URL: ' + match.getAnchorText()
* } );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test Load URL: google.com
*/
Autolinker.HtmlTag = Autolinker.Util.extend( Object, {
/**
* @cfg {String} tagName
*
* The tag name. Ex: 'a', 'button', etc.
*
* Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString}
* is executed.
*/
/**
* @cfg {Object.} attrs
*
* An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the
* values are the attribute values.
*/
/**
* @cfg {String} innerHtml
*
* The inner HTML for the tag.
*
* Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym
* naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML}
* if you prefer, but this one is recommended.
*/
/**
* @cfg {String} innerHTML
*
* Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version
* for acronym names.
*/
/**
* @protected
* @property {RegExp} whitespaceRegex
*
* Regular expression used to match whitespace in a string of CSS classes.
*/
whitespaceRegex : /\s+/,
/**
* @constructor
* @param {Object} [cfg] The configuration properties for this class, in an Object (map)
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym
},
/**
* Sets the tag name that will be used to generate the tag with.
*
* @param {String} tagName
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setTagName : function( tagName ) {
this.tagName = tagName;
return this;
},
/**
* Retrieves the tag name.
*
* @return {String}
*/
getTagName : function() {
return this.tagName || "";
},
/**
* Sets an attribute on the HtmlTag.
*
* @param {String} attrName The attribute name to set.
* @param {String} attrValue The attribute value to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setAttr : function( attrName, attrValue ) {
var tagAttrs = this.getAttrs();
tagAttrs[ attrName ] = attrValue;
return this;
},
/**
* Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.
*
* @param {String} name The attribute name to retrieve.
* @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
*/
getAttr : function( attrName ) {
return this.getAttrs()[ attrName ];
},
/**
* Sets one or more attributes on the HtmlTag.
*
* @param {Object.} attrs A key/value Object (map) of the attributes to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setAttrs : function( attrs ) {
var tagAttrs = this.getAttrs();
Autolinker.Util.assign( tagAttrs, attrs );
return this;
},
/**
* Retrieves the attributes Object (map) for the HtmlTag.
*
* @return {Object.} A key/value object of the attributes for the HtmlTag.
*/
getAttrs : function() {
return this.attrs || ( this.attrs = {} );
},
/**
* Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.
*
* @param {String} cssClass One or more space-separated CSS classes to set (overwrite).
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setClass : function( cssClass ) {
return this.setAttr( 'class', cssClass );
},
/**
* Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.
*
* @param {String} cssClass One or more space-separated CSS classes to add.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
addClass : function( cssClass ) {
var classAttr = this.getClass(),
whitespaceRegex = this.whitespaceRegex,
indexOf = Autolinker.Util.indexOf, // to support IE8 and below
classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),
newClasses = cssClass.split( whitespaceRegex ),
newClass;
while( newClass = newClasses.shift() ) {
if( indexOf( classes, newClass ) === -1 ) {
classes.push( newClass );
}
}
this.getAttrs()[ 'class' ] = classes.join( " " );
return this;
},
/**
* Convenience method to remove one or more CSS classes from the HtmlTag.
*
* @param {String} cssClass One or more space-separated CSS classes to remove.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
removeClass : function( cssClass ) {
var classAttr = this.getClass(),
whitespaceRegex = this.whitespaceRegex,
indexOf = Autolinker.Util.indexOf, // to support IE8 and below
classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),
removeClasses = cssClass.split( whitespaceRegex ),
removeClass;
while( classes.length && ( removeClass = removeClasses.shift() ) ) {
var idx = indexOf( classes, removeClass );
if( idx !== -1 ) {
classes.splice( idx, 1 );
}
}
this.getAttrs()[ 'class' ] = classes.join( " " );
return this;
},
/**
* Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when
* there are multiple.
*
* @return {String}
*/
getClass : function() {
return this.getAttrs()[ 'class' ] || "";
},
/**
* Convenience method to check if the tag has a CSS class or not.
*
* @param {String} cssClass The CSS class to check for.
* @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.
*/
hasClass : function( cssClass ) {
return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1;
},
/**
* Sets the inner HTML for the tag.
*
* @param {String} html The inner HTML to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setInnerHtml : function( html ) {
this.innerHtml = html;
return this;
},
/**
* Retrieves the inner HTML for the tag.
*
* @return {String}
*/
getInnerHtml : function() {
return this.innerHtml || "";
},
/**
* Override of superclass method used to generate the HTML string for the tag.
*
* @return {String}
*/
toAnchorString : function() {
var tagName = this.getTagName(),
attrsStr = this.buildAttrsStr();
attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes
return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '', tagName, '>' ].join( "" );
},
/**
* Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate
* the stringified HtmlTag.
*
* @protected
* @return {String} Example return: `attr1="value1" attr2="value2"`
*/
buildAttrsStr : function() {
if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string
var attrs = this.getAttrs(),
attrsArr = [];
for( var prop in attrs ) {
if( attrs.hasOwnProperty( prop ) ) {
attrsArr.push( prop + '="' + attrs[ prop ] + '"' );
}
}
return attrsArr.join( " " );
}
} );
/*global Autolinker */
/*jshint sub:true */
/**
* @protected
* @class Autolinker.AnchorTagBuilder
* @extends Object
*
* Builds anchor (<a>) tags for the Autolinker utility when a match is found.
*
* Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may
* actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances
* which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example:
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance
* tag.setAttr( 'rel', 'nofollow' );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test google.com
*/
Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, {
/**
* @cfg {Boolean} newWindow
* @inheritdoc Autolinker#newWindow
*/
/**
* @cfg {Number} truncate
* @inheritdoc Autolinker#truncate
*/
/**
* @cfg {String} className
* @inheritdoc Autolinker#className
*/
/**
* @constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
},
/**
* Generates the actual anchor (<a>) tag to use in place of the
* matched text, via its `match` object.
*
* @param {Autolinker.match.Match} match The Match instance to generate an
* anchor tag from.
* @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag.
*/
build : function( match ) {
var tag = new Autolinker.HtmlTag( {
tagName : 'a',
attrs : this.createAttrs( match.getType(), match.getAnchorHref() ),
innerHtml : this.processAnchorText( match.getAnchorText() )
} );
return tag;
},
/**
* Creates the Object (map) of the HTML attributes for the anchor (<a>)
* tag being generated.
*
* @protected
* @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of
* match that an anchor tag is being generated for.
* @param {String} href The href for the anchor tag.
* @return {Object} A key/value Object (map) of the anchor tag's attributes.
*/
createAttrs : function( matchType, anchorHref ) {
var attrs = {
'href' : anchorHref // we'll always have the `href` attribute
};
var cssClass = this.createCssClass( matchType );
if( cssClass ) {
attrs[ 'class' ] = cssClass;
}
if( this.newWindow ) {
attrs[ 'target' ] = "_blank";
}
return attrs;
},
/**
* Creates the CSS class that will be used for a given anchor tag, based on
* the `matchType` and the {@link #className} config.
*
* @private
* @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of
* match that an anchor tag is being generated for.
* @return {String} The CSS class string for the link. Example return:
* "myLink myLink-url". If no {@link #className} was configured, returns
* an empty string.
*/
createCssClass : function( matchType ) {
var className = this.className;
if( !className )
return "";
else
return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", "myLink myLink-phone", "myLink myLink-twitter", or "myLink myLink-hashtag"
},
/**
* Processes the `anchorText` by truncating the text according to the
* {@link #truncate} config.
*
* @private
* @param {String} anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return {String} The processed `anchorText`.
*/
processAnchorText : function( anchorText ) {
anchorText = this.doTruncate( anchorText );
return anchorText;
},
/**
* Performs the truncation of the `anchorText`, if the `anchorText` is
* longer than the {@link #truncate} option. Truncates the text to 2
* characters fewer than the {@link #truncate} option, and adds ".." to the
* end.
*
* @private
* @param {String} text The anchor tag's text (i.e. what will be displayed).
* @return {String} The truncated anchor text.
*/
doTruncate : function( anchorText ) {
return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY );
}
} );
/*global Autolinker */
/**
* @private
* @class Autolinker.htmlParser.HtmlParser
* @extends Object
*
* An HTML parser implementation which simply walks an HTML string and returns an array of
* {@link Autolinker.htmlParser.HtmlNode HtmlNodes} that represent the basic HTML structure of the input string.
*
* Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, effectively ignoring / "walking
* around" HTML tags.
*/
Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, {
/**
* @private
* @property {RegExp} htmlRegex
*
* The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and
* attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html.
*
* Capturing groups:
*
* 1. The "!DOCTYPE" tag name, if a tag is a <!DOCTYPE> tag.
* 2. If it is an end tag, this group will have the '/'.
* 3. If it is a comment tag, this group will hold the comment text (i.e.
* the text inside the `<!--` and `-->`.
* 4. The tag name for all tags (other than the <!DOCTYPE> tag)
*/
htmlRegex : (function() {
var commentTagRegex = /!--([\s\S]+?)--/,
tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/,
attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char
attrValueRegex = /(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values
nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]'
return new RegExp( [
// for tag. Ex: )
'(?:',
'<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag
// Zero or more attributes following the tag name
'(?:',
'\\s+', // one or more whitespace chars before an attribute
// Either:
// A. attr="value", or
// B. "value" alone (To cover example doctype tag: )
'(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')',
')*',
'>',
')',
'|',
// All other HTML tags (i.e. tags that are not )
'(?:',
'<(/)?', // Beginning of a tag or comment. Either '<' for a start tag, or '' for an end tag.
// *** Capturing Group 2: The slash or an empty string. Slash ('/') for end tag, empty string for start or self-closing tag.
'(?:',
commentTagRegex.source, // *** Capturing Group 3 - A Comment Tag's Text
'|',
'(?:',
// *** Capturing Group 4 - The tag name
'(' + tagNameRegex.source + ')',
// Zero or more attributes following the tag name
'(?:',
'\\s+', // one or more whitespace chars before an attribute
nameEqualsValueRegex, // attr="value" (with optional ="value" part)
')*',
'\\s*/?', // any trailing spaces and optional '/' before the closing '>'
')',
')',
'>',
')'
].join( "" ), 'gi' );
} )(),
/**
* @private
* @property {RegExp} htmlCharacterEntitiesRegex
*
* The regular expression that matches common HTML character entities.
*
* Ignoring & as it could be part of a query string -- handling it separately.
*/
htmlCharacterEntitiesRegex: /( | |<|<|>|>|"|"|')/gi,
/**
* Parses an HTML string and returns a simple array of {@link Autolinker.htmlParser.HtmlNode HtmlNodes}
* to represent the HTML structure of the input string.
*
* @param {String} html The HTML to parse.
* @return {Autolinker.htmlParser.HtmlNode[]}
*/
parse : function( html ) {
var htmlRegex = this.htmlRegex,
currentResult,
lastIndex = 0,
textAndEntityNodes,
nodes = []; // will be the result of the method
while( ( currentResult = htmlRegex.exec( html ) ) !== null ) {
var tagText = currentResult[ 0 ],
commentText = currentResult[ 3 ], // if we've matched a comment
tagName = currentResult[ 1 ] || currentResult[ 4 ], // The tag (ex: "!DOCTYPE"), or another tag (ex: "a" or "img")
isClosingTag = !!currentResult[ 2 ],
inBetweenTagsText = html.substring( lastIndex, currentResult.index );
// Push TextNodes and EntityNodes for any text found between tags
if( inBetweenTagsText ) {
textAndEntityNodes = this.parseTextAndEntityNodes( inBetweenTagsText );
nodes.push.apply( nodes, textAndEntityNodes );
}
// Push the CommentNode or ElementNode
if( commentText ) {
nodes.push( this.createCommentNode( tagText, commentText ) );
} else {
nodes.push( this.createElementNode( tagText, tagName, isClosingTag ) );
}
lastIndex = currentResult.index + tagText.length;
}
// Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements.
if( lastIndex < html.length ) {
var text = html.substring( lastIndex );
// Push TextNodes and EntityNodes for any text found between tags
if( text ) {
textAndEntityNodes = this.parseTextAndEntityNodes( text );
nodes.push.apply( nodes, textAndEntityNodes );
}
}
return nodes;
},
/**
* Parses text and HTML entity nodes from a given string. The input string
* should not have any HTML tags (elements) within it.
*
* @private
* @param {String} text The text to parse.
* @return {Autolinker.htmlParser.HtmlNode[]} An array of HtmlNodes to
* represent the {@link Autolinker.htmlParser.TextNode TextNodes} and
* {@link Autolinker.htmlParser.EntityNode EntityNodes} found.
*/
parseTextAndEntityNodes : function( text ) {
var nodes = [],
textAndEntityTokens = Autolinker.Util.splitAndCapture( text, this.htmlCharacterEntitiesRegex ); // split at HTML entities, but include the HTML entities in the results array
// Every even numbered token is a TextNode, and every odd numbered token is an EntityNode
// For example: an input `text` of "Test "this" today" would turn into the
// `textAndEntityTokens`: [ 'Test ', '"', 'this', '"', ' today' ]
for( var i = 0, len = textAndEntityTokens.length; i < len; i += 2 ) {
var textToken = textAndEntityTokens[ i ],
entityToken = textAndEntityTokens[ i + 1 ];
if( textToken ) nodes.push( this.createTextNode( textToken ) );
if( entityToken ) nodes.push( this.createEntityNode( entityToken ) );
}
return nodes;
},
/**
* Factory method to create an {@link Autolinker.htmlParser.CommentNode CommentNode}.
*
* @private
* @param {String} tagText The full text of the tag (comment) that was
* matched, including its <!-- and -->.
* @param {String} comment The full text of the comment that was matched.
*/
createCommentNode : function( tagText, commentText ) {
return new Autolinker.htmlParser.CommentNode( {
text: tagText,
comment: Autolinker.Util.trim( commentText )
} );
},
/**
* Factory method to create an {@link Autolinker.htmlParser.ElementNode ElementNode}.
*
* @private
* @param {String} tagText The full text of the tag (element) that was
* matched, including its attributes.
* @param {String} tagName The name of the tag. Ex: An <img> tag would
* be passed to this method as "img".
* @param {Boolean} isClosingTag `true` if it's a closing tag, false
* otherwise.
* @return {Autolinker.htmlParser.ElementNode}
*/
createElementNode : function( tagText, tagName, isClosingTag ) {
return new Autolinker.htmlParser.ElementNode( {
text : tagText,
tagName : tagName.toLowerCase(),
closing : isClosingTag
} );
},
/**
* Factory method to create a {@link Autolinker.htmlParser.EntityNode EntityNode}.
*
* @private
* @param {String} text The text that was matched for the HTML entity (such
* as ' ').
* @return {Autolinker.htmlParser.EntityNode}
*/
createEntityNode : function( text ) {
return new Autolinker.htmlParser.EntityNode( { text: text } );
},
/**
* Factory method to create a {@link Autolinker.htmlParser.TextNode TextNode}.
*
* @private
* @param {String} text The text that was matched.
* @return {Autolinker.htmlParser.TextNode}
*/
createTextNode : function( text ) {
return new Autolinker.htmlParser.TextNode( { text: text } );
}
} );
/*global Autolinker */
/**
* @abstract
* @class Autolinker.htmlParser.HtmlNode
*
* Represents an HTML node found in an input string. An HTML node is one of the following:
*
* 1. An {@link Autolinker.htmlParser.ElementNode ElementNode}, which represents HTML tags.
* 2. A {@link Autolinker.htmlParser.TextNode TextNode}, which represents text outside or within HTML tags.
* 3. A {@link Autolinker.htmlParser.EntityNode EntityNode}, which represents one of the known HTML
* entities that Autolinker looks for. This includes common ones such as " and
*/
Autolinker.htmlParser.HtmlNode = Autolinker.Util.extend( Object, {
/**
* @cfg {String} text (required)
*
* The original text that was matched for the HtmlNode.
*
* - In the case of an {@link Autolinker.htmlParser.ElementNode ElementNode}, this will be the tag's
* text.
* - In the case of a {@link Autolinker.htmlParser.TextNode TextNode}, this will be the text itself.
* - In the case of a {@link Autolinker.htmlParser.EntityNode EntityNode}, this will be the text of
* the HTML entity.
*/
text : "",
/**
* @constructor
* @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map).
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
},
/**
* Returns a string name for the type of node that this class represents.
*
* @abstract
* @return {String}
*/
getType : Autolinker.Util.abstractMethod,
/**
* Retrieves the {@link #text} for the HtmlNode.
*
* @return {String}
*/
getText : function() {
return this.text;
}
} );
/*global Autolinker */
/**
* @class Autolinker.htmlParser.CommentNode
* @extends Autolinker.htmlParser.HtmlNode
*
* Represents an HTML comment node that has been parsed by the
* {@link Autolinker.htmlParser.HtmlParser}.
*
* See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more
* details.
*/
Autolinker.htmlParser.CommentNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
/**
* @cfg {String} comment (required)
*
* The text inside the comment tag. This text is stripped of any leading or
* trailing whitespace.
*/
comment : '',
/**
* Returns a string name for the type of node that this class represents.
*
* @return {String}
*/
getType : function() {
return 'comment';
},
/**
* Returns the comment inside the comment tag.
*
* @return {String}
*/
getComment : function() {
return this.comment;
}
} );
/*global Autolinker */
/**
* @class Autolinker.htmlParser.ElementNode
* @extends Autolinker.htmlParser.HtmlNode
*
* Represents an HTML element node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}.
*
* See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details.
*/
Autolinker.htmlParser.ElementNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
/**
* @cfg {String} tagName (required)
*
* The name of the tag that was matched.
*/
tagName : '',
/**
* @cfg {Boolean} closing (required)
*
* `true` if the element (tag) is a closing tag, `false` if its an opening tag.
*/
closing : false,
/**
* Returns a string name for the type of node that this class represents.
*
* @return {String}
*/
getType : function() {
return 'element';
},
/**
* Returns the HTML element's (tag's) name. Ex: for an <img> tag, returns "img".
*
* @return {String}
*/
getTagName : function() {
return this.tagName;
},
/**
* Determines if the HTML element (tag) is a closing tag. Ex: <div> returns
* `false`, while </div> returns `true`.
*
* @return {Boolean}
*/
isClosing : function() {
return this.closing;
}
} );
/*global Autolinker */
/**
* @class Autolinker.htmlParser.EntityNode
* @extends Autolinker.htmlParser.HtmlNode
*
* Represents a known HTML entity node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}.
* Ex: ' ', or '&#160;' (which will be retrievable from the {@link #getText} method.
*
* Note that this class will only be returned from the HtmlParser for the set of checked HTML entity nodes
* defined by the {@link Autolinker.htmlParser.HtmlParser#htmlCharacterEntitiesRegex}.
*
* See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details.
*/
Autolinker.htmlParser.EntityNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
/**
* Returns a string name for the type of node that this class represents.
*
* @return {String}
*/
getType : function() {
return 'entity';
}
} );
/*global Autolinker */
/**
* @class Autolinker.htmlParser.TextNode
* @extends Autolinker.htmlParser.HtmlNode
*
* Represents a text node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}.
*
* See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details.
*/
Autolinker.htmlParser.TextNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
/**
* Returns a string name for the type of node that this class represents.
*
* @return {String}
*/
getType : function() {
return 'text';
}
} );
/*global Autolinker */
/**
* @private
* @class Autolinker.matchParser.MatchParser
* @extends Object
*
* Used by Autolinker to parse potential matches, given an input string of text.
*
* The MatchParser is fed a non-HTML string in order to search for matches.
* Autolinker first uses the {@link Autolinker.htmlParser.HtmlParser} to "walk
* around" HTML tags, and then the text around the HTML tags is passed into the
* MatchParser in order to find the actual matches.
*/
Autolinker.matchParser.MatchParser = Autolinker.Util.extend( Object, {
/**
* @cfg {Boolean} urls
* @inheritdoc Autolinker#urls
*/
urls : true,
/**
* @cfg {Boolean} email
* @inheritdoc Autolinker#email
*/
email : true,
/**
* @cfg {Boolean} twitter
* @inheritdoc Autolinker#twitter
*/
twitter : true,
/**
* @cfg {Boolean} phone
* @inheritdoc Autolinker#phone
*/
phone: true,
/**
* @cfg {Boolean/String} hashtag
* @inheritdoc Autolinker#hashtag
*/
hashtag : false,
/**
* @cfg {Boolean} stripPrefix
* @inheritdoc Autolinker#stripPrefix
*/
stripPrefix : true,
/**
* @private
* @property {RegExp} matcherRegex
*
* The regular expression that matches URLs, email addresses, phone #s,
* Twitter handles, and Hashtags.
*
* This regular expression has the following capturing groups:
*
* 1. Group that is used to determine if there is a Twitter handle match
* (i.e. \@someTwitterUser). Simply check for its existence to determine
* if there is a Twitter handle match. The next couple of capturing
* groups give information about the Twitter handle match.
* 2. The whitespace character before the \@sign in a Twitter handle. This
* is needed because there are no lookbehinds in JS regular expressions,
* and can be used to reconstruct the original string in a replace().
* 3. The Twitter handle itself in a Twitter match. If the match is
* '@someTwitterUser', the handle is 'someTwitterUser'.
* 4. Group that matches an email address. Used to determine if the match
* is an email address, as well as holding the full address. Ex:
* 'me@my.com'
* 5. Group that matches a URL in the input text. Ex: 'http://google.com',
* 'www.google.com', or just 'google.com'. This also includes a path,
* url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor
* 6. Group that matches a protocol URL (i.e. 'http://google.com'). This is
* used to match protocol URLs with just a single word, like 'http://localhost',
* where we won't double check that the domain name has at least one '.'
* in it.
* 7. A protocol-relative ('//') match for the case of a 'www.' prefixed
* URL. Will be an empty string if it is not a protocol-relative match.
* We need to know the character before the '//' in order to determine
* if it is a valid match or the // was in a string we don't want to
* auto-link.
* 8. A protocol-relative ('//') match for the case of a known TLD prefixed
* URL. Will be an empty string if it is not a protocol-relative match.
* See #6 for more info.
* 9. Group that is used to determine if there is a phone number match. The
* next 3 groups give segments of the phone number.
* 10. Group that is used to determine if there is a Hashtag match
* (i.e. \#someHashtag). Simply check for its existence to determine if
* there is a Hashtag match. The next couple of capturing groups give
* information about the Hashtag match.
* 11. The whitespace character before the #sign in a Hashtag handle. This
* is needed because there are no look-behinds in JS regular
* expressions, and can be used to reconstruct the original string in a
* replace().
* 12. The Hashtag itself in a Hashtag match. If the match is
* '#someHashtag', the hashtag is 'someHashtag'.
*/
matcherRegex : (function() {
var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs
hashtagRegex = /(^|[^\w])#(\w{1,15})/, // For matching a Hashtag. Ex: #games
emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part)
phoneRegex = /(?:\+?\d{1,3}[-\s.])?\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}/, // ex: (123) 456-7890, 123 456 7890, 123-456-7890, etc.
protocolRegex = /(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\/\/)(?!\d+\/?)(?:\/\/)?)/, // match protocol, allow in format "http://" or "mailto:". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match "link:"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex)
wwwRegex = /(?:www\.)/, // starting with 'www.'
domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period
tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs)
// Allow optional path, query string, and hash anchor, not ending in the following characters: "?!:,.;"
// http://blog.codinghorror.com/the-problem-with-urls/
urlSuffixRegex = /[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]?!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]]/;
return new RegExp( [
'(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace()
// *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and
// *** Capturing group $3, which matches the actual twitter handle
twitterRegex.source,
')',
'|',
'(', // *** Capturing group $4, which is used to determine an email match
emailRegex.source,
domainNameRegex.source,
tldRegex.source,
')',
'|',
'(', // *** Capturing group $5, which is used to match a URL
'(?:', // parens to cover match for protocol (optional), and domain
'(', // *** Capturing group $6, for a protocol-prefixed url (ex: http://google.com)
protocolRegex.source,
domainNameRegex.source,
')',
'|',
'(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com)
'(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character
wwwRegex.source,
domainNameRegex.source,
')',
'|',
'(?:', // non-capturing paren for known a TLD url (ex: google.com)
'(.?//)?', // *** Capturing group $8 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character
domainNameRegex.source,
tldRegex.source,
')',
')',
'(?:' + urlSuffixRegex.source + ')?', // match for path, query string, and/or hash anchor - optional
')',
'|',
// this setup does not scale well for open extension :( Need to rethink design of autolinker...
// *** Capturing group $9, which matches a (USA for now) phone number
'(',
phoneRegex.source,
')',
'|',
'(', // *** Capturing group $10, which can be used to check for a Hashtag match. Use group $12 for the actual Hashtag though. $11 may be used to reconstruct the original string in a replace()
// *** Capturing group $11, which matches the whitespace character before the '#' sign (needed because of no lookbehinds), and
// *** Capturing group $12, which matches the actual Hashtag
hashtagRegex.source,
')'
].join( "" ), 'gi' );
} )(),
/**
* @private
* @property {RegExp} charBeforeProtocolRelMatchRegex
*
* The regular expression used to retrieve the character before a
* protocol-relative URL match.
*
* This is used in conjunction with the {@link #matcherRegex}, which needs
* to grab the character before a protocol-relative '//' due to the lack of
* a negative look-behind in JavaScript regular expressions. The character
* before the match is stripped from the URL.
*/
charBeforeProtocolRelMatchRegex : /^(.)?\/\//,
/**
* @private
* @property {Autolinker.MatchValidator} matchValidator
*
* The MatchValidator object, used to filter out any false positives from
* the {@link #matcherRegex}. See {@link Autolinker.MatchValidator} for details.
*/
/**
* @constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder
* instance, specified in an Object (map).
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
this.matchValidator = new Autolinker.MatchValidator();
},
/**
* Parses the input `text` to search for matches, and calls the `replaceFn`
* to allow replacements of the matches. Returns the `text` with matches
* replaced.
*
* @param {String} text The text to search and repace matches in.
* @param {Function} replaceFn The iterator function to handle the
* replacements. The function takes a single argument, a {@link Autolinker.match.Match}
* object, and should return the text that should make the replacement.
* @param {Object} [contextObj=window] The context object ("scope") to run
* the `replaceFn` in.
* @return {String}
*/
replace : function( text, replaceFn, contextObj ) {
var me = this; // for closure
return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ) {
var matchDescObj = me.processCandidateMatch( matchStr, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ); // "match description" object
// Return out with no changes for match types that are disabled (url,
// email, phone, etc.), or for matches that are invalid (false
// positives from the matcherRegex, which can't use look-behinds
// since they are unavailable in JS).
if( !matchDescObj ) {
return matchStr;
} else {
// Generate replacement text for the match from the `replaceFn`
var replaceStr = replaceFn.call( contextObj, matchDescObj.match );
return matchDescObj.prefixStr + replaceStr + matchDescObj.suffixStr;
}
} );
},
/**
* Processes a candidate match from the {@link #matcherRegex}.
*
* Not all matches found by the regex are actual URL/Email/Phone/Twitter/Hashtag
* matches, as determined by the {@link #matchValidator}. In this case, the
* method returns `null`. Otherwise, a valid Object with `prefixStr`,
* `match`, and `suffixStr` is returned.
*
* @private
* @param {String} matchStr The full match that was found by the
* {@link #matcherRegex}.
* @param {String} twitterMatch The matched text of a Twitter handle, if the
* match is a Twitter match.
* @param {String} twitterHandlePrefixWhitespaceChar The whitespace char
* before the @ sign in a Twitter handle match. This is needed because of
* no lookbehinds in JS regexes, and is need to re-include the character
* for the anchor tag replacement.
* @param {String} twitterHandle The actual Twitter user (i.e the word after
* the @ sign in a Twitter match).
* @param {String} emailAddressMatch The matched email address for an email
* address match.
* @param {String} urlMatch The matched URL string for a URL match.
* @param {String} protocolUrlMatch The match URL string for a protocol
* match. Ex: 'http://yahoo.com'. This is used to match something like
* 'http://localhost', where we won't double check that the domain name
* has at least one '.' in it.
* @param {String} wwwProtocolRelativeMatch The '//' for a protocol-relative
* match from a 'www' url, with the character that comes before the '//'.
* @param {String} tldProtocolRelativeMatch The '//' for a protocol-relative
* match from a TLD (top level domain) match, with the character that
* comes before the '//'.
* @param {String} phoneMatch The matched text of a phone number
* @param {String} hashtagMatch The matched text of a Twitter
* Hashtag, if the match is a Hashtag match.
* @param {String} hashtagPrefixWhitespaceChar The whitespace char
* before the # sign in a Hashtag match. This is needed because of no
* lookbehinds in JS regexes, and is need to re-include the character for
* the anchor tag replacement.
* @param {String} hashtag The actual Hashtag (i.e the word
* after the # sign in a Hashtag match).
*
* @return {Object} A "match description object". This will be `null` if the
* match was invalid, or if a match type is disabled. Otherwise, this will
* be an Object (map) with the following properties:
* @return {String} return.prefixStr The char(s) that should be prepended to
* the replacement string. These are char(s) that were needed to be
* included from the regex match that were ignored by processing code, and
* should be re-inserted into the replacement stream.
* @return {String} return.suffixStr The char(s) that should be appended to
* the replacement string. These are char(s) that were needed to be
* included from the regex match that were ignored by processing code, and
* should be re-inserted into the replacement stream.
* @return {Autolinker.match.Match} return.match The Match object that
* represents the match that was found.
*/
processCandidateMatch : function(
matchStr, twitterMatch, twitterHandlePrefixWhitespaceChar, twitterHandle,
emailAddressMatch, urlMatch, protocolUrlMatch, wwwProtocolRelativeMatch,
tldProtocolRelativeMatch, phoneMatch, hashtagMatch,
hashtagPrefixWhitespaceChar, hashtag
) {
// Note: The `matchStr` variable wil be fixed up to remove characters that are no longer needed (which will
// be added to `prefixStr` and `suffixStr`).
var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch,
match, // Will be an Autolinker.match.Match object
prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter and Hashtag matches.
suffixStr = ""; // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked.
// Return out with `null` for match types that are disabled (url, email,
// twitter, hashtag), or for matches that are invalid (false positives
// from the matcherRegex, which can't use look-behinds since they are
// unavailable in JS).
if(
( urlMatch && !this.urls ) ||
( emailAddressMatch && !this.email ) ||
( phoneMatch && !this.phone ) ||
( twitterMatch && !this.twitter ) ||
( hashtagMatch && !this.hashtag ) ||
!this.matchValidator.isValidMatch( urlMatch, protocolUrlMatch, protocolRelativeMatch )
) {
return null;
}
// Handle a closing parenthesis at the end of the match, and exclude it
// if there is not a matching open parenthesis
// in the match itself.
if( this.matchHasUnbalancedClosingParen( matchStr ) ) {
matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")"
suffixStr = ")"; // this will be added after the generated tag
}
if( emailAddressMatch ) {
match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } );
} else if( twitterMatch ) {
// fix up the `matchStr` if there was a preceding whitespace char,
// which was needed to determine the match itself (since there are
// no look-behinds in JS regexes)
if( twitterHandlePrefixWhitespaceChar ) {
prefixStr = twitterHandlePrefixWhitespaceChar;
matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match
}
match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } );
} else if( phoneMatch ) {
// remove non-numeric values from phone number string
var cleanNumber = matchStr.replace( /\D/g, '' );
match = new Autolinker.match.Phone( { matchedText: matchStr, number: cleanNumber } );
} else if( hashtagMatch ) {
// fix up the `matchStr` if there was a preceding whitespace char,
// which was needed to determine the match itself (since there are
// no look-behinds in JS regexes)
if( hashtagPrefixWhitespaceChar ) {
prefixStr = hashtagPrefixWhitespaceChar;
matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match
}
match = new Autolinker.match.Hashtag( { matchedText: matchStr, serviceName: this.hashtag, hashtag: hashtag } );
} else { // url match
// If it's a protocol-relative '//' match, remove the character
// before the '//' (which the matcherRegex needed to match due to
// the lack of a negative look-behind in JavaScript regular
// expressions)
if( protocolRelativeMatch ) {
var charBeforeMatch = protocolRelativeMatch.match( this.charBeforeProtocolRelMatchRegex )[ 1 ] || "";
if( charBeforeMatch ) { // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes)
prefixStr = charBeforeMatch;
matchStr = matchStr.slice( 1 ); // remove the prefixed char from the match
}
}
match = new Autolinker.match.Url( {
matchedText : matchStr,
url : matchStr,
protocolUrlMatch : !!protocolUrlMatch,
protocolRelativeMatch : !!protocolRelativeMatch,
stripPrefix : this.stripPrefix
} );
}
return {
prefixStr : prefixStr,
suffixStr : suffixStr,
match : match
};
},
/**
* Determines if a match found has an unmatched closing parenthesis. If so,
* this parenthesis will be removed from the match itself, and appended
* after the generated anchor tag in {@link #processCandidateMatch}.
*
* A match may have an extra closing parenthesis at the end of the match
* because the regular expression must include parenthesis for URLs such as
* "wikipedia.com/something_(disambiguation)", which should be auto-linked.
*
* However, an extra parenthesis *will* be included when the URL itself is
* wrapped in parenthesis, such as in the case of "(wikipedia.com/something_(disambiguation))".
* In this case, the last closing parenthesis should *not* be part of the
* URL itself, and this method will return `true`.
*
* @private
* @param {String} matchStr The full match string from the {@link #matcherRegex}.
* @return {Boolean} `true` if there is an unbalanced closing parenthesis at
* the end of the `matchStr`, `false` otherwise.
*/
matchHasUnbalancedClosingParen : function( matchStr ) {
var lastChar = matchStr.charAt( matchStr.length - 1 );
if( lastChar === ')' ) {
var openParensMatch = matchStr.match( /\(/g ),
closeParensMatch = matchStr.match( /\)/g ),
numOpenParens = ( openParensMatch && openParensMatch.length ) || 0,
numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0;
if( numOpenParens < numCloseParens ) {
return true;
}
}
return false;
}
} );
/*global Autolinker */
/*jshint scripturl:true */
/**
* @private
* @class Autolinker.MatchValidator
* @extends Object
*
* Used by Autolinker to filter out false positives from the
* {@link Autolinker.matchParser.MatchParser#matcherRegex}.
*
* Due to the limitations of regular expressions (including the missing feature
* of look-behinds in JS regular expressions), we cannot always determine the
* validity of a given match. This class applies a bit of additional logic to
* filter out any false positives that have been matched by the
* {@link Autolinker.matchParser.MatchParser#matcherRegex}.
*/
Autolinker.MatchValidator = Autolinker.Util.extend( Object, {
/**
* @private
* @property {RegExp} invalidProtocolRelMatchRegex
*
* The regular expression used to check a potential protocol-relative URL
* match, coming from the {@link Autolinker.matchParser.MatchParser#matcherRegex}.
* A protocol-relative URL is, for example, "//yahoo.com"
*
* This regular expression checks to see if there is a word character before
* the '//' match in order to determine if we should actually autolink a
* protocol-relative URL. This is needed because there is no negative
* look-behind in JavaScript regular expressions.
*
* For instance, we want to autolink something like "Go to: //google.com",
* but we don't want to autolink something like "abc//google.com"
*/
invalidProtocolRelMatchRegex : /^[\w]\/\//,
/**
* Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://'
*
* @private
* @property {RegExp} hasFullProtocolRegex
*/
hasFullProtocolRegex : /^[A-Za-z][-.+A-Za-z0-9]+:\/\//,
/**
* Regex to find the URI scheme, such as 'mailto:'.
*
* This is used to filter out 'javascript:' and 'vbscript:' schemes.
*
* @private
* @property {RegExp} uriSchemeRegex
*/
uriSchemeRegex : /^[A-Za-z][-.+A-Za-z0-9]+:/,
/**
* Regex to determine if at least one word char exists after the protocol (i.e. after the ':')
*
* @private
* @property {RegExp} hasWordCharAfterProtocolRegex
*/
hasWordCharAfterProtocolRegex : /:[^\s]*?[A-Za-z]/,
/**
* Determines if a given match found by the {@link Autolinker.matchParser.MatchParser}
* is valid. Will return `false` for:
*
* 1) URL matches which do not have at least have one period ('.') in the
* domain name (effectively skipping over matches like "abc:def").
* However, URL matches with a protocol will be allowed (ex: 'http://localhost')
* 2) URL matches which do not have at least one word character in the
* domain name (effectively skipping over matches like "git:1.0").
* 3) A protocol-relative url match (a URL beginning with '//') whose
* previous character is a word character (effectively skipping over
* strings like "abc//google.com")
*
* Otherwise, returns `true`.
*
* @param {String} urlMatch The matched URL, if there was one. Will be an
* empty string if the match is not a URL match.
* @param {String} protocolUrlMatch The match URL string for a protocol
* match. Ex: 'http://yahoo.com'. This is used to match something like
* 'http://localhost', where we won't double check that the domain name
* has at least one '.' in it.
* @param {String} protocolRelativeMatch The protocol-relative string for a
* URL match (i.e. '//'), possibly with a preceding character (ex, a
* space, such as: ' //', or a letter, such as: 'a//'). The match is
* invalid if there is a word character preceding the '//'.
* @return {Boolean} `true` if the match given is valid and should be
* processed, or `false` if the match is invalid and/or should just not be
* processed.
*/
isValidMatch : function( urlMatch, protocolUrlMatch, protocolRelativeMatch ) {
if(
( protocolUrlMatch && !this.isValidUriScheme( protocolUrlMatch ) ) ||
this.urlMatchDoesNotHaveProtocolOrDot( urlMatch, protocolUrlMatch ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost')
this.urlMatchDoesNotHaveAtLeastOneWordChar( urlMatch, protocolUrlMatch ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0"
this.isInvalidProtocolRelativeMatch( protocolRelativeMatch ) // A protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com")
) {
return false;
}
return true;
},
/**
* Determines if the URI scheme is a valid scheme to be autolinked. Returns
* `false` if the scheme is 'javascript:' or 'vbscript:'
*
* @private
* @param {String} uriSchemeMatch The match URL string for a full URI scheme
* match. Ex: 'http://yahoo.com' or 'mailto:a@a.com'.
* @return {Boolean} `true` if the scheme is a valid one, `false` otherwise.
*/
isValidUriScheme : function( uriSchemeMatch ) {
var uriScheme = uriSchemeMatch.match( this.uriSchemeRegex )[ 0 ].toLowerCase();
return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' );
},
/**
* Determines if a URL match does not have either:
*
* a) a full protocol (i.e. 'http://'), or
* b) at least one dot ('.') in the domain name (for a non-full-protocol
* match).
*
* Either situation is considered an invalid URL (ex: 'git:d' does not have
* either the '://' part, or at least one dot in the domain name. If the
* match was 'git:abc.com', we would consider this valid.)
*
* @private
* @param {String} urlMatch The matched URL, if there was one. Will be an
* empty string if the match is not a URL match.
* @param {String} protocolUrlMatch The match URL string for a protocol
* match. Ex: 'http://yahoo.com'. This is used to match something like
* 'http://localhost', where we won't double check that the domain name
* has at least one '.' in it.
* @return {Boolean} `true` if the URL match does not have a full protocol,
* or at least one dot ('.') in a non-full-protocol match.
*/
urlMatchDoesNotHaveProtocolOrDot : function( urlMatch, protocolUrlMatch ) {
return ( !!urlMatch && ( !protocolUrlMatch || !this.hasFullProtocolRegex.test( protocolUrlMatch ) ) && urlMatch.indexOf( '.' ) === -1 );
},
/**
* Determines if a URL match does not have at least one word character after
* the protocol (i.e. in the domain name).
*
* At least one letter character must exist in the domain name after a
* protocol match. Ex: skip over something like "git:1.0"
*
* @private
* @param {String} urlMatch The matched URL, if there was one. Will be an
* empty string if the match is not a URL match.
* @param {String} protocolUrlMatch The match URL string for a protocol
* match. Ex: 'http://yahoo.com'. This is used to know whether or not we
* have a protocol in the URL string, in order to check for a word
* character after the protocol separator (':').
* @return {Boolean} `true` if the URL match does not have at least one word
* character in it after the protocol, `false` otherwise.
*/
urlMatchDoesNotHaveAtLeastOneWordChar : function( urlMatch, protocolUrlMatch ) {
if( urlMatch && protocolUrlMatch ) {
return !this.hasWordCharAfterProtocolRegex.test( urlMatch );
} else {
return false;
}
},
/**
* Determines if a protocol-relative match is an invalid one. This method
* returns `true` if there is a `protocolRelativeMatch`, and that match
* contains a word character before the '//' (i.e. it must contain
* whitespace or nothing before the '//' in order to be considered valid).
*
* @private
* @param {String} protocolRelativeMatch The protocol-relative string for a
* URL match (i.e. '//'), possibly with a preceding character (ex, a
* space, such as: ' //', or a letter, such as: 'a//'). The match is
* invalid if there is a word character preceding the '//'.
* @return {Boolean} `true` if it is an invalid protocol-relative match,
* `false` otherwise.
*/
isInvalidProtocolRelativeMatch : function( protocolRelativeMatch ) {
return ( !!protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) );
}
} );
/*global Autolinker */
/**
* @abstract
* @class Autolinker.match.Match
*
* Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a
* {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, and Twitter Handles
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( autolinker, match ) {
* console.log( "href = ", match.getAnchorHref() );
* console.log( "text = ", match.getAnchorText() );
*
* switch( match.getType() ) {
* case 'url' :
* console.log( "url: ", match.getUrl() );
*
* case 'email' :
* console.log( "email: ", match.getEmail() );
*
* case 'twitter' :
* console.log( "twitter: ", match.getTwitterHandle() );
* }
* }
* } );
*
* See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.
*/
Autolinker.match.Match = Autolinker.Util.extend( Object, {
/**
* @cfg {String} matchedText (required)
*
* The original text that was matched.
*/
/**
* @constructor
* @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map).
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
},
/**
* Returns a string name for the type of match that this class represents.
*
* @abstract
* @return {String}
*/
getType : Autolinker.Util.abstractMethod,
/**
* Returns the original text that was matched.
*
* @return {String}
*/
getMatchedText : function() {
return this.matchedText;
},
/**
* Returns the anchor href that should be generated for the match.
*
* @abstract
* @return {String}
*/
getAnchorHref : Autolinker.Util.abstractMethod,
/**
* Returns the anchor text that should be generated for the match.
*
* @abstract
* @return {String}
*/
getAnchorText : Autolinker.Util.abstractMethod
} );
/*global Autolinker */
/**
* @class Autolinker.match.Email
* @extends Autolinker.match.Match
*
* Represents a Email match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, {
/**
* @cfg {String} email (required)
*
* The email address that was matched.
*/
/**
* Returns a string name for the type of match that this class represents.
*
* @return {String}
*/
getType : function() {
return 'email';
},
/**
* Returns the email address that was matched.
*
* @return {String}
*/
getEmail : function() {
return this.email;
},
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref : function() {
return 'mailto:' + this.email;
},
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText : function() {
return this.email;
}
} );
/*global Autolinker */
/**
* @class Autolinker.match.Hashtag
* @extends Autolinker.match.Match
*
* Represents a Hashtag match found in an input string which should be
* Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more
* details.
*/
Autolinker.match.Hashtag = Autolinker.Util.extend( Autolinker.match.Match, {
/**
* @cfg {String} serviceName (required)
*
* The service to point hashtag matches to. See {@link Autolinker#hashtag}
* for available values.
*/
/**
* @cfg {String} hashtag (required)
*
* The Hashtag that was matched, without the '#'.
*/
/**
* Returns the type of match that this class represents.
*
* @return {String}
*/
getType : function() {
return 'hashtag';
},
/**
* Returns the matched hashtag.
*
* @return {String}
*/
getHashtag : function() {
return this.hashtag;
},
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref : function() {
var serviceName = this.serviceName,
hashtag = this.hashtag;
switch( serviceName ) {
case 'twitter' :
return 'https://twitter.com/hashtag/' + hashtag;
case 'facebook' :
return 'https://www.facebook.com/hashtag/' + hashtag;
default : // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case.
throw new Error( 'Unknown service name to point hashtag to: ', serviceName );
}
},
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText : function() {
return '#' + this.hashtag;
}
} );
/*global Autolinker */
/**
* @class Autolinker.match.Phone
* @extends Autolinker.match.Match
*
* Represents a Phone number match found in an input string which should be
* Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more
* details.
*/
Autolinker.match.Phone = Autolinker.Util.extend( Autolinker.match.Match, {
/**
* @cfg {String} number (required)
*
* The phone number that was matched.
*/
/**
* Returns a string name for the type of match that this class represents.
*
* @return {String}
*/
getType : function() {
return 'phone';
},
/**
* Returns the phone number that was matched.
*
* @return {String}
*/
getNumber: function() {
return this.number;
},
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref : function() {
return 'tel:' + this.number;
},
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText : function() {
return this.matchedText;
}
} );
/*global Autolinker */
/**
* @class Autolinker.match.Twitter
* @extends Autolinker.match.Match
*
* Represents a Twitter match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, {
/**
* @cfg {String} twitterHandle (required)
*
* The Twitter handle that was matched.
*/
/**
* Returns the type of match that this class represents.
*
* @return {String}
*/
getType : function() {
return 'twitter';
},
/**
* Returns a string name for the type of match that this class represents.
*
* @return {String}
*/
getTwitterHandle : function() {
return this.twitterHandle;
},
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref : function() {
return 'https://twitter.com/' + this.twitterHandle;
},
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText : function() {
return '@' + this.twitterHandle;
}
} );
/*global Autolinker */
/**
* @class Autolinker.match.Url
* @extends Autolinker.match.Match
*
* Represents a Url match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
/**
* @cfg {String} url (required)
*
* The url that was matched.
*/
/**
* @cfg {Boolean} protocolUrlMatch (required)
*
* `true` if the URL is a match which already has a protocol (i.e. 'http://'), `false` if the match was from a 'www' or
* known TLD match.
*/
/**
* @cfg {Boolean} protocolRelativeMatch (required)
*
* `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//',
* and will be either http:// or https:// based on the protocol that the site is loaded under.
*/
/**
* @cfg {Boolean} stripPrefix (required)
* @inheritdoc Autolinker#stripPrefix
*/
/**
* @private
* @property {RegExp} urlPrefixRegex
*
* A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs.
*/
urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i,
/**
* @private
* @property {RegExp} protocolRelativeRegex
*
* The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes
* of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com"
*/
protocolRelativeRegex : /^\/\//,
/**
* @private
* @property {Boolean} protocolPrepended
*
* Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the
* {@link #url} did not have a protocol)
*/
protocolPrepended : false,
/**
* Returns a string name for the type of match that this class represents.
*
* @return {String}
*/
getType : function() {
return 'url';
},
/**
* Returns the url that was matched, assuming the protocol to be 'http://' if the original
* match was missing a protocol.
*
* @return {String}
*/
getUrl : function() {
var url = this.url;
// if the url string doesn't begin with a protocol, assume 'http://'
if( !this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended ) {
url = this.url = 'http://' + url;
this.protocolPrepended = true;
}
return url;
},
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref : function() {
var url = this.getUrl();
return url.replace( /&/g, '&' ); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html
},
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText : function() {
var anchorText = this.getUrl();
if( this.protocolRelativeMatch ) {
// Strip off any protocol-relative '//' from the anchor text
anchorText = this.stripProtocolRelativePrefix( anchorText );
}
if( this.stripPrefix ) {
anchorText = this.stripUrlPrefix( anchorText );
}
anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one
return anchorText;
},
// ---------------------------------------
// Utility Functionality
/**
* Strips the URL prefix (such as "http://" or "https://") from the given text.
*
* @private
* @param {String} text The text of the anchor that is being generated, for which to strip off the
* url prefix (such as stripping off "http://")
* @return {String} The `anchorText`, with the prefix stripped.
*/
stripUrlPrefix : function( text ) {
return text.replace( this.urlPrefixRegex, '' );
},
/**
* Strips any protocol-relative '//' from the anchor text.
*
* @private
* @param {String} text The text of the anchor that is being generated, for which to strip off the
* protocol-relative prefix (such as stripping off "//")
* @return {String} The `anchorText`, with the protocol-relative prefix stripped.
*/
stripProtocolRelativePrefix : function( text ) {
return text.replace( this.protocolRelativeRegex, '' );
},
/**
* Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.
*
* @private
* @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing
* slash ('/') that may exist.
* @return {String} The `anchorText`, with the trailing slash removed.
*/
removeTrailingSlash : function( anchorText ) {
if( anchorText.charAt( anchorText.length - 1 ) === '/' ) {
anchorText = anchorText.slice( 0, -1 );
}
return anchorText;
}
} );
return Autolinker;
}));
/**
@license
Copyright (c) 2013 Gildas Lormeau. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/
define('ThirdParty/zip',[
'../Core/buildModuleUrl',
'../Core/defineProperties'
], function(
buildModuleUrl,
defineProperties) {
var tmp = {};
(function(obj) {
var ERR_BAD_FORMAT = "File format is not recognized.";
var ERR_ENCRYPTED = "File contains encrypted entry.";
var ERR_ZIP64 = "File is using Zip64 (4gb+ file size).";
var ERR_READ = "Error while reading zip file.";
var ERR_WRITE = "Error while writing zip file.";
var ERR_WRITE_DATA = "Error while writing file data.";
var ERR_READ_DATA = "Error while reading file data.";
var ERR_DUPLICATED_NAME = "File already exists.";
var CHUNK_SIZE = 512 * 1024;
var INFLATE_JS = "inflate.js";
var DEFLATE_JS = "deflate.js";
var TEXT_PLAIN = "text/plain";
var MESSAGE_EVENT = "message";
var appendABViewSupported;
try {
appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0;
} catch (e) {
}
function Crc32() {
var crc = -1, that = this;
that.append = function(data) {
var offset, table = that.table;
for (offset = 0; offset < data.length; offset++)
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
};
that.get = function() {
return ~crc;
};
}
Crc32.prototype.table = (function() {
var i, j, t, table = [];
for (i = 0; i < 256; i++) {
t = i;
for (j = 0; j < 8; j++)
if (t & 1)
t = (t >>> 1) ^ 0xEDB88320;
else
t = t >>> 1;
table[i] = t;
}
return table;
})();
function blobSlice(blob, index, length) {
if (blob.slice)
return blob.slice(index, index + length);
else if (blob.webkitSlice)
return blob.webkitSlice(index, index + length);
else if (blob.mozSlice)
return blob.mozSlice(index, index + length);
else if (blob.msSlice)
return blob.msSlice(index, index + length);
}
function getDataHelper(byteLength, bytes) {
var dataBuffer, dataArray;
dataBuffer = new ArrayBuffer(byteLength);
dataArray = new Uint8Array(dataBuffer);
if (bytes)
dataArray.set(bytes, 0);
return {
buffer : dataBuffer,
array : dataArray,
view : new DataView(dataBuffer)
};
}
// Readers
function Reader() {
}
function TextReader(text) {
var that = this, blobReader;
function init(callback, onerror) {
var blob = new Blob([ text ], {
type : TEXT_PLAIN
});
blobReader = new BlobReader(blob);
blobReader.init(function() {
that.size = blobReader.size;
callback();
}, onerror);
}
function readUint8Array(index, length, callback, onerror) {
blobReader.readUint8Array(index, length, callback, onerror);
}
that.size = 0;
that.init = init;
that.readUint8Array = readUint8Array;
}
TextReader.prototype = new Reader();
TextReader.prototype.constructor = TextReader;
function Data64URIReader(dataURI) {
var that = this, dataStart;
function init(callback) {
var dataEnd = dataURI.length;
while (dataURI.charAt(dataEnd - 1) == "=")
dataEnd--;
dataStart = dataURI.indexOf(",") + 1;
that.size = Math.floor((dataEnd - dataStart) * 0.75);
callback();
}
function readUint8Array(index, length, callback) {
var i, data = getDataHelper(length);
var start = Math.floor(index / 3) * 4;
var end = Math.ceil((index + length) / 3) * 4;
var bytes = window.atob(dataURI.substring(start + dataStart, end + dataStart));
var delta = index - Math.floor(start / 4) * 3;
for (i = delta; i < delta + length; i++)
data.array[i - delta] = bytes.charCodeAt(i);
callback(data.array);
}
that.size = 0;
that.init = init;
that.readUint8Array = readUint8Array;
}
Data64URIReader.prototype = new Reader();
Data64URIReader.prototype.constructor = Data64URIReader;
function BlobReader(blob) {
var that = this;
function init(callback) {
this.size = blob.size;
callback();
}
function readUint8Array(index, length, callback, onerror) {
var reader = new FileReader();
reader.onload = function(e) {
callback(new Uint8Array(e.target.result));
};
reader.onerror = onerror;
reader.readAsArrayBuffer(blobSlice(blob, index, length));
}
that.size = 0;
that.init = init;
that.readUint8Array = readUint8Array;
}
BlobReader.prototype = new Reader();
BlobReader.prototype.constructor = BlobReader;
// Writers
function Writer() {
}
Writer.prototype.getData = function(callback) {
callback(this.data);
};
function TextWriter(encoding) {
var that = this, blob;
function init(callback) {
blob = new Blob([], {
type : TEXT_PLAIN
});
callback();
}
function writeUint8Array(array, callback) {
blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
type : TEXT_PLAIN
});
callback();
}
function getData(callback, onerror) {
var reader = new FileReader();
reader.onload = function(e) {
callback(e.target.result);
};
reader.onerror = onerror;
reader.readAsText(blob, encoding);
}
that.init = init;
that.writeUint8Array = writeUint8Array;
that.getData = getData;
}
TextWriter.prototype = new Writer();
TextWriter.prototype.constructor = TextWriter;
function Data64URIWriter(contentType) {
var that = this, data = "", pending = "";
function init(callback) {
data += "data:" + (contentType || "") + ";base64,";
callback();
}
function writeUint8Array(array, callback) {
var i, delta = pending.length, dataString = pending;
pending = "";
for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++)
dataString += String.fromCharCode(array[i]);
for (; i < array.length; i++)
pending += String.fromCharCode(array[i]);
if (dataString.length > 2)
data += window.btoa(dataString);
else
pending = dataString;
callback();
}
function getData(callback) {
callback(data + window.btoa(pending));
}
that.init = init;
that.writeUint8Array = writeUint8Array;
that.getData = getData;
}
Data64URIWriter.prototype = new Writer();
Data64URIWriter.prototype.constructor = Data64URIWriter;
function BlobWriter(contentType) {
var blob, that = this;
function init(callback) {
blob = new Blob([], {
type : contentType
});
callback();
}
function writeUint8Array(array, callback) {
blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
type : contentType
});
callback();
}
function getData(callback) {
callback(blob);
}
that.init = init;
that.writeUint8Array = writeUint8Array;
that.getData = getData;
}
BlobWriter.prototype = new Writer();
BlobWriter.prototype.constructor = BlobWriter;
// inflate/deflate core functions
function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
var chunkIndex = 0, index, outputSize;
function onflush() {
worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
onend(outputSize);
}
function onmessage(event) {
var message = event.data, data = message.data;
if (message.onappend) {
outputSize += data.length;
writer.writeUint8Array(data, function() {
onappend(false, data);
step();
}, onwriteerror);
}
if (message.onflush)
if (data) {
outputSize += data.length;
writer.writeUint8Array(data, function() {
onappend(false, data);
onflush();
}, onwriteerror);
} else
onflush();
if (message.progress && onprogress)
onprogress(index + message.current, size);
}
function step() {
index = chunkIndex * CHUNK_SIZE;
if (index < size)
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
worker.postMessage({
append : true,
data : array
});
chunkIndex++;
if (onprogress)
onprogress(index, size);
onappend(true, array);
}, onreaderror);
else
worker.postMessage({
flush : true
});
}
outputSize = 0;
worker.addEventListener(MESSAGE_EVENT, onmessage, false);
step();
}
function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
var chunkIndex = 0, index, outputSize = 0;
function step() {
var outputData;
index = chunkIndex * CHUNK_SIZE;
if (index < size)
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) {
var outputData = process.append(inputData, function() {
if (onprogress)
onprogress(offset + index, size);
});
outputSize += outputData.length;
onappend(true, inputData);
writer.writeUint8Array(outputData, function() {
onappend(false, outputData);
chunkIndex++;
setTimeout(step, 1);
}, onwriteerror);
if (onprogress)
onprogress(index, size);
}, onreaderror);
else {
outputData = process.flush();
if (outputData) {
outputSize += outputData.length;
writer.writeUint8Array(outputData, function() {
onappend(false, outputData);
onend(outputSize);
}, onwriteerror);
} else
onend(outputSize);
}
}
step();
}
function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
var worker, crc32 = new Crc32();
function oninflateappend(sending, array) {
if (computeCrc32 && !sending)
crc32.append(array);
}
function oninflateend(outputSize) {
onend(outputSize, crc32.get());
}
if (obj.zip.useWebWorkers) {
worker = new Worker(obj.zip.workerScriptsPath + INFLATE_JS);
launchWorkerProcess(worker, reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
} else
launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
return worker;
}
function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) {
var worker, crc32 = new Crc32();
function ondeflateappend(sending, array) {
if (sending)
crc32.append(array);
}
function ondeflateend(outputSize) {
onend(outputSize, crc32.get());
}
function onmessage() {
worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
}
if (obj.zip.useWebWorkers) {
worker = new Worker(obj.zip.workerScriptsPath + DEFLATE_JS);
worker.addEventListener(MESSAGE_EVENT, onmessage, false);
worker.postMessage({
init : true,
level : level
});
} else
launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
return worker;
}
function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
var chunkIndex = 0, crc32 = new Crc32();
function step() {
var index = chunkIndex * CHUNK_SIZE;
if (index < size)
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
if (computeCrc32)
crc32.append(array);
if (onprogress)
onprogress(index, size, array);
writer.writeUint8Array(array, function() {
chunkIndex++;
step();
}, onwriteerror);
}, onreaderror);
else
onend(size, crc32.get());
}
step();
}
// ZipReader
function decodeASCII(str) {
var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB',
'\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9',
'\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1',
'\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6',
'\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3',
'\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE',
'\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE',
'\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7',
'\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ];
for (i = 0; i < str.length; i++) {
charCode = str.charCodeAt(i) & 0xFF;
if (charCode > 127)
out += extendedASCII[charCode - 128];
else
out += String.fromCharCode(charCode);
}
return out;
}
function decodeUTF8(string) {
return decodeURIComponent(escape(string));
}
function getString(bytes) {
var i, str = "";
for (i = 0; i < bytes.length; i++)
str += String.fromCharCode(bytes[i]);
return str;
}
function getDate(timeRaw) {
var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
try {
return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5,
(time & 0x001F) * 2, 0);
} catch (e) {
}
}
function readCommonHeader(entry, data, index, centralDirectory, onerror) {
entry.version = data.view.getUint16(index, true);
entry.bitFlag = data.view.getUint16(index + 2, true);
entry.compressionMethod = data.view.getUint16(index + 4, true);
entry.lastModDateRaw = data.view.getUint32(index + 6, true);
entry.lastModDate = getDate(entry.lastModDateRaw);
if ((entry.bitFlag & 0x01) === 0x01) {
onerror(ERR_ENCRYPTED);
return;
}
if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) {
entry.crc32 = data.view.getUint32(index + 10, true);
entry.compressedSize = data.view.getUint32(index + 14, true);
entry.uncompressedSize = data.view.getUint32(index + 18, true);
}
if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) {
onerror(ERR_ZIP64);
return;
}
entry.filenameLength = data.view.getUint16(index + 22, true);
entry.extraFieldLength = data.view.getUint16(index + 24, true);
}
function createZipReader(reader, onerror) {
function Entry() {
}
Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) {
var that = this, worker;
function terminate(callback, param) {
if (worker)
worker.terminate();
worker = null;
if (callback)
callback(param);
}
function testCrc32(crc32) {
var dataCrc32 = getDataHelper(4);
dataCrc32.view.setUint32(0, crc32);
return that.crc32 == dataCrc32.view.getUint32(0);
}
function getWriterData(uncompressedSize, crc32) {
if (checkCrc32 && !testCrc32(crc32))
onreaderror();
else
writer.getData(function(data) {
terminate(onend, data);
});
}
function onreaderror() {
terminate(onerror, ERR_READ_DATA);
}
function onwriteerror() {
terminate(onerror, ERR_WRITE_DATA);
}
reader.readUint8Array(that.offset, 30, function(bytes) {
var data = getDataHelper(bytes.length, bytes), dataOffset;
if (data.view.getUint32(0) != 0x504b0304) {
onerror(ERR_BAD_FORMAT);
return;
}
readCommonHeader(that, data, 4, false, onerror);
dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength;
writer.init(function() {
if (that.compressionMethod === 0)
copy(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
else
worker = inflate(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
}, onwriteerror);
}, onreaderror);
};
function seekEOCDR(offset, entriesCallback) {
reader.readUint8Array(reader.size - offset, offset, function(bytes) {
var dataView = getDataHelper(bytes.length, bytes).view;
if (dataView.getUint32(0) != 0x504b0506) {
seekEOCDR(offset + 1, entriesCallback);
} else {
entriesCallback(dataView);
}
}, function() {
onerror(ERR_READ);
});
}
return {
getEntries : function(callback) {
if (reader.size < 22) {
onerror(ERR_BAD_FORMAT);
return;
}
// look for End of central directory record
seekEOCDR(22, function(dataView) {
var datalength, fileslength;
datalength = dataView.getUint32(16, true);
fileslength = dataView.getUint16(8, true);
reader.readUint8Array(datalength, reader.size - datalength, function(bytes) {
var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes);
for (i = 0; i < fileslength; i++) {
entry = new Entry();
if (data.view.getUint32(index) != 0x504b0102) {
onerror(ERR_BAD_FORMAT);
return;
}
readCommonHeader(entry, data, index + 6, true, onerror);
entry.commentLength = data.view.getUint16(index + 32, true);
entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10);
entry.offset = data.view.getUint32(index + 42, true);
filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength));
entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename);
if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/")
entry.directory = true;
comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46
+ entry.filenameLength + entry.extraFieldLength + entry.commentLength));
entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment);
entries.push(entry);
index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength;
}
callback(entries);
}, function() {
onerror(ERR_READ);
});
});
},
close : function(callback) {
if (callback)
callback();
}
};
}
// ZipWriter
function encodeUTF8(string) {
return unescape(encodeURIComponent(string));
}
function getBytes(str) {
var i, array = [];
for (i = 0; i < str.length; i++)
array.push(str.charCodeAt(i));
return array;
}
function createZipWriter(writer, onerror, dontDeflate) {
var worker, files = {}, filenames = [], datalength = 0;
function terminate(callback, message) {
if (worker)
worker.terminate();
worker = null;
if (callback)
callback(message);
}
function onwriteerror() {
terminate(onerror, ERR_WRITE);
}
function onreaderror() {
terminate(onerror, ERR_READ_DATA);
}
return {
add : function(name, reader, onend, onprogress, options) {
var header, filename, date;
function writeHeader(callback) {
var data;
date = options.lastModDate || new Date();
header = getDataHelper(26);
files[name] = {
headerArray : header.array,
directory : options.directory,
filename : filename,
offset : datalength,
comment : getBytes(encodeUTF8(options.comment || ""))
};
header.view.setUint32(0, 0x14000808);
if (options.version)
header.view.setUint8(0, options.version);
if (!dontDeflate && options.level !== 0 && !options.directory)
header.view.setUint16(4, 0x0800);
header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
header.view.setUint16(22, filename.length, true);
data = getDataHelper(30 + filename.length);
data.view.setUint32(0, 0x504b0304);
data.array.set(header.array, 4);
data.array.set(filename, 30);
datalength += data.array.length;
writer.writeUint8Array(data.array, callback, onwriteerror);
}
function writeFooter(compressedLength, crc32) {
var footer = getDataHelper(16);
datalength += compressedLength || 0;
footer.view.setUint32(0, 0x504b0708);
if (typeof crc32 != "undefined") {
header.view.setUint32(10, crc32, true);
footer.view.setUint32(4, crc32, true);
}
if (reader) {
footer.view.setUint32(8, compressedLength, true);
header.view.setUint32(14, compressedLength, true);
footer.view.setUint32(12, reader.size, true);
header.view.setUint32(18, reader.size, true);
}
writer.writeUint8Array(footer.array, function() {
datalength += 16;
terminate(onend);
}, onwriteerror);
}
function writeFile() {
options = options || {};
name = name.trim();
if (options.directory && name.charAt(name.length - 1) != "/")
name += "/";
if (files.hasOwnProperty(name)) {
onerror(ERR_DUPLICATED_NAME);
return;
}
filename = getBytes(encodeUTF8(name));
filenames.push(name);
writeHeader(function() {
if (reader)
if (dontDeflate || options.level === 0)
copy(reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
else
worker = deflate(reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror);
else
writeFooter();
}, onwriteerror);
}
if (reader)
reader.init(writeFile, onreaderror);
else
writeFile();
},
close : function(callback) {
var data, length = 0, index = 0, indexFilename, file;
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
file = files[filenames[indexFilename]];
length += 46 + file.filename.length + file.comment.length;
}
data = getDataHelper(length + 22);
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
file = files[filenames[indexFilename]];
data.view.setUint32(index, 0x504b0102);
data.view.setUint16(index + 4, 0x1400);
data.array.set(file.headerArray, index + 6);
data.view.setUint16(index + 32, file.comment.length, true);
if (file.directory)
data.view.setUint8(index + 38, 0x10);
data.view.setUint32(index + 42, file.offset, true);
data.array.set(file.filename, index + 46);
data.array.set(file.comment, index + 46 + file.filename.length);
index += 46 + file.filename.length + file.comment.length;
}
data.view.setUint32(index, 0x504b0506);
data.view.setUint16(index + 8, filenames.length, true);
data.view.setUint16(index + 10, filenames.length, true);
data.view.setUint32(index + 12, length, true);
data.view.setUint32(index + 16, datalength, true);
writer.writeUint8Array(data.array, function() {
terminate(function() {
writer.getData(callback);
});
}, onwriteerror);
}
};
}
obj.zip = {
Reader : Reader,
Writer : Writer,
BlobReader : BlobReader,
Data64URIReader : Data64URIReader,
TextReader : TextReader,
BlobWriter : BlobWriter,
Data64URIWriter : Data64URIWriter,
TextWriter : TextWriter,
createReader : function(reader, callback, onerror) {
reader.init(function() {
callback(createZipReader(reader, onerror));
}, onerror);
},
createWriter : function(writer, callback, onerror, dontDeflate) {
writer.init(function() {
callback(createZipWriter(writer, onerror, dontDeflate));
}, onerror);
},
useWebWorkers : true
};
var workerScriptsPath;
defineProperties(obj.zip, {
'workerScriptsPath' : {
get : function() {
if (typeof workerScriptsPath === 'undefined') {
workerScriptsPath = buildModuleUrl('ThirdParty/Workers/');
}
return workerScriptsPath;
}
}
});
})(tmp);
return tmp.zip;
});
/*global define*/
define('DataSources/KmlDataSource',[
'../Core/AssociativeArray',
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/ClockRange',
'../Core/ClockStep',
'../Core/Color',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/Event',
'../Core/getAbsoluteUri',
'../Core/getExtensionFromUri',
'../Core/getFilenameFromUri',
'../Core/Iso8601',
'../Core/joinUrls',
'../Core/JulianDate',
'../Core/loadBlob',
'../Core/loadXML',
'../Core/Math',
'../Core/NearFarScalar',
'../Core/PinBuilder',
'../Core/PolygonHierarchy',
'../Core/Rectangle',
'../Core/RuntimeError',
'../Core/TimeInterval',
'../Core/TimeIntervalCollection',
'../Scene/HeightReference',
'../Scene/HorizontalOrigin',
'../Scene/LabelStyle',
'../Scene/SceneMode',
'../ThirdParty/Autolinker',
'../ThirdParty/Uri',
'../ThirdParty/when',
'../ThirdParty/zip',
'./BillboardGraphics',
'./CompositePositionProperty',
'./CorridorGraphics',
'./DataSource',
'./DataSourceClock',
'./Entity',
'./EntityCluster',
'./EntityCollection',
'./LabelGraphics',
'./PathGraphics',
'./PolygonGraphics',
'./PolylineGraphics',
'./PositionPropertyArray',
'./RectangleGraphics',
'./ReferenceProperty',
'./SampledPositionProperty',
'./ScaledPositionProperty',
'./TimeIntervalCollectionProperty',
'./WallGraphics'
], function(
AssociativeArray,
BoundingRectangle,
Cartesian2,
Cartesian3,
Cartographic,
ClockRange,
ClockStep,
Color,
createGuid,
defaultValue,
defined,
defineProperties,
DeveloperError,
Ellipsoid,
Event,
getAbsoluteUri,
getExtensionFromUri,
getFilenameFromUri,
Iso8601,
joinUrls,
JulianDate,
loadBlob,
loadXML,
CesiumMath,
NearFarScalar,
PinBuilder,
PolygonHierarchy,
Rectangle,
RuntimeError,
TimeInterval,
TimeIntervalCollection,
HeightReference,
HorizontalOrigin,
LabelStyle,
SceneMode,
Autolinker,
Uri,
when,
zip,
BillboardGraphics,
CompositePositionProperty,
CorridorGraphics,
DataSource,
DataSourceClock,
Entity,
EntityCluster,
EntityCollection,
LabelGraphics,
PathGraphics,
PolygonGraphics,
PolylineGraphics,
PositionPropertyArray,
RectangleGraphics,
ReferenceProperty,
SampledPositionProperty,
ScaledPositionProperty,
TimeIntervalCollectionProperty,
WallGraphics) {
'use strict';
// IE 8 doesn't have a DOM parser and can't run Cesium anyway, so just bail.
if (typeof DOMParser === 'undefined') {
return {};
}
//This is by no means an exhaustive list of MIME types.
//The purpose of this list is to be able to accurately identify content embedded
//in KMZ files. Eventually, we can make this configurable by the end user so they can add
//there own content types if they have KMZ files that require it.
var MimeTypes = {
avi : "video/x-msvideo",
bmp : "image/bmp",
bz2 : "application/x-bzip2",
chm : "application/vnd.ms-htmlhelp",
css : "text/css",
csv : "text/csv",
doc : "application/msword",
dvi : "application/x-dvi",
eps : "application/postscript",
flv : "video/x-flv",
gif : "image/gif",
gz : "application/x-gzip",
htm : "text/html",
html : "text/html",
ico : "image/vnd.microsoft.icon",
jnlp : "application/x-java-jnlp-file",
jpeg : "image/jpeg",
jpg : "image/jpeg",
m3u : "audio/x-mpegurl",
m4v : "video/mp4",
mathml : "application/mathml+xml",
mid : "audio/midi",
midi : "audio/midi",
mov : "video/quicktime",
mp3 : "audio/mpeg",
mp4 : "video/mp4",
mp4v : "video/mp4",
mpeg : "video/mpeg",
mpg : "video/mpeg",
odp : "application/vnd.oasis.opendocument.presentation",
ods : "application/vnd.oasis.opendocument.spreadsheet",
odt : "application/vnd.oasis.opendocument.text",
ogg : "application/ogg",
pdf : "application/pdf",
png : "image/png",
pps : "application/vnd.ms-powerpoint",
ppt : "application/vnd.ms-powerpoint",
ps : "application/postscript",
qt : "video/quicktime",
rdf : "application/rdf+xml",
rss : "application/rss+xml",
rtf : "application/rtf",
svg : "image/svg+xml",
swf : "application/x-shockwave-flash",
text : "text/plain",
tif : "image/tiff",
tiff : "image/tiff",
txt : "text/plain",
wav : "audio/x-wav",
wma : "audio/x-ms-wma",
wmv : "video/x-ms-wmv",
xml : "application/xml",
zip : "application/zip",
detectFromFilename : function(filename) {
var ext = filename.toLowerCase();
ext = getExtensionFromUri(ext);
return MimeTypes[ext];
}
};
var parser = new DOMParser();
var autolinker = new Autolinker({
stripPrefix : false,
twitter : false,
email : false,
replaceFn : function(linker, match) {
if (!match.protocolUrlMatch) {
//Prevent matching of non-explicit urls.
//i.e. foo.id won't match but http://foo.id will
return false;
}
}
});
var BILLBOARD_SIZE = 32;
var BILLBOARD_NEAR_DISTANCE = 2414016;
var BILLBOARD_NEAR_RATIO = 1.0;
var BILLBOARD_FAR_DISTANCE = 1.6093e+7;
var BILLBOARD_FAR_RATIO = 0.1;
function isZipFile(blob) {
var magicBlob = blob.slice(0, Math.min(4, blob.size));
var deferred = when.defer();
var reader = new FileReader();
reader.addEventListener('load', function() {
deferred.resolve(new DataView(reader.result).getUint32(0, false) === 0x504b0304);
});
reader.addEventListener('error', function() {
deferred.reject(reader.error);
});
reader.readAsArrayBuffer(magicBlob);
return deferred.promise;
}
function readBlobAsText(blob) {
var deferred = when.defer();
var reader = new FileReader();
reader.addEventListener('load', function() {
deferred.resolve(reader.result);
});
reader.addEventListener('error', function() {
deferred.reject(reader.error);
});
reader.readAsText(blob);
return deferred.promise;
}
function loadXmlFromZip(reader, entry, uriResolver, deferred) {
entry.getData(new zip.TextWriter(), function(text) {
uriResolver.kml = parser.parseFromString(text, 'application/xml');
deferred.resolve();
});
}
function loadDataUriFromZip(reader, entry, uriResolver, deferred) {
var mimeType = defaultValue(MimeTypes.detectFromFilename(entry.filename), 'application/octet-stream');
entry.getData(new zip.Data64URIWriter(mimeType), function(dataUri) {
uriResolver[entry.filename] = dataUri;
deferred.resolve();
});
}
function replaceAttributes(div, elementType, attributeName, uriResolver) {
var keys = uriResolver.keys;
var baseUri = new Uri('.');
var elements = div.querySelectorAll(elementType);
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var value = element.getAttribute(attributeName);
var uri = new Uri(value).resolve(baseUri).toString();
var index = keys.indexOf(uri);
if (index !== -1) {
var key = keys[index];
element.setAttribute(attributeName, uriResolver[key]);
if (elementType === 'a' && element.getAttribute('download') === null) {
element.setAttribute('download', key);
}
}
}
}
function proxyUrl(url, proxy) {
if (defined(proxy)) {
if (new Uri(url).isAbsolute()) {
url = proxy.getURL(url);
}
}
return url;
}
// an optional context is passed to allow for some malformed kmls (those with multiple geometries with same ids) to still parse
// correctly, as they do in Google Earth.
function createEntity(node, entityCollection, context) {
var id = queryStringAttribute(node, 'id');
id = defined(id) && id.length !== 0 ? id : createGuid();
if(defined(context)){
id = context + id;
}
// If we have a duplicate ID just generate one.
// This isn't valid KML but Google Earth handles this case.
var entity = entityCollection.getById(id);
if (defined(entity)) {
id = createGuid();
if(defined(context)){
id = context + id;
}
}
entity = entityCollection.add(new Entity({id : id}));
if (!defined(entity.kml)) {
entity.addProperty('kml');
entity.kml = new KmlFeatureData();
}
return entity;
}
function isExtrudable(altitudeMode, gxAltitudeMode) {
return altitudeMode === 'absolute' || altitudeMode === 'relativeToGround' || gxAltitudeMode === 'relativeToSeaFloor';
}
function readCoordinate(value) {
//Google Earth treats empty or missing coordinates as 0.
if (!defined(value)) {
return Cartesian3.fromDegrees(0, 0, 0);
}
var digits = value.match(/[^\s,\n]+/g);
if (!defined(digits)) {
return Cartesian3.fromDegrees(0, 0, 0);
}
var longitude = parseFloat(digits[0]);
var latitude = parseFloat(digits[1]);
var height = parseFloat(digits[2]);
longitude = isNaN(longitude) ? 0.0 : longitude;
latitude = isNaN(latitude) ? 0.0 : latitude;
height = isNaN(height) ? 0.0 : height;
return Cartesian3.fromDegrees(longitude, latitude, height);
}
function readCoordinates(element) {
if (!defined(element)) {
return undefined;
}
var tuples = element.textContent.match(/[^\s\n]+/g);
if (!defined(tuples)) {
return undefined;
}
var length = tuples.length;
var result = new Array(length);
var resultIndex = 0;
for (var i = 0; i < length; i++) {
result[resultIndex++] = readCoordinate(tuples[i]);
}
return result;
}
var kmlNamespaces = [null, undefined, 'http://www.opengis.net/kml/2.2', 'http://earth.google.com/kml/2.2', 'http://earth.google.com/kml/2.1', 'http://earth.google.com/kml/2.0'];
var gxNamespaces = ['http://www.google.com/kml/ext/2.2'];
var atomNamespaces = ['http://www.w3.org/2005/Atom'];
var namespaces = {
kml : kmlNamespaces,
gx : gxNamespaces,
atom : atomNamespaces,
kmlgx : kmlNamespaces.concat(gxNamespaces)
};
function queryNumericAttribute(node, attributeName) {
if (!defined(node)) {
return undefined;
}
var value = node.getAttribute(attributeName);
if (value !== null) {
var result = parseFloat(value);
return !isNaN(result) ? result : undefined;
}
return undefined;
}
function queryStringAttribute(node, attributeName) {
if (!defined(node)) {
return undefined;
}
var value = node.getAttribute(attributeName);
return value !== null ? value : undefined;
}
function queryFirstNode(node, tagName, namespace) {
if (!defined(node)) {
return undefined;
}
var childNodes = node.childNodes;
var length = childNodes.length;
for (var q = 0; q < length; q++) {
var child = childNodes[q];
if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
return child;
}
}
return undefined;
}
function queryNodes(node, tagName, namespace) {
if (!defined(node)) {
return undefined;
}
var result = [];
var childNodes = node.getElementsByTagNameNS('*', tagName);
var length = childNodes.length;
for (var q = 0; q < length; q++) {
var child = childNodes[q];
if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
result.push(child);
}
}
return result;
}
function queryChildNodes(node, tagName, namespace) {
if (!defined(node)) {
return [];
}
var result = [];
var childNodes = node.childNodes;
var length = childNodes.length;
for (var q = 0; q < length; q++) {
var child = childNodes[q];
if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
result.push(child);
}
}
return result;
}
function queryNumericValue(node, tagName, namespace) {
var resultNode = queryFirstNode(node, tagName, namespace);
if (defined(resultNode)) {
var result = parseFloat(resultNode.textContent);
return !isNaN(result) ? result : undefined;
}
return undefined;
}
function queryStringValue(node, tagName, namespace) {
var result = queryFirstNode(node, tagName, namespace);
if (defined(result)) {
return result.textContent.trim();
}
return undefined;
}
function queryBooleanValue(node, tagName, namespace) {
var result = queryFirstNode(node, tagName, namespace);
if (defined(result)) {
var value = result.textContent.trim();
return value === '1' || /^true$/i.test(value);
}
return undefined;
}
function resolveHref(href, proxy, sourceUri, uriResolver) {
if (!defined(href)) {
return undefined;
}
var hrefResolved = false;
if (defined(uriResolver)) {
var blob = uriResolver[href];
if (defined(blob)) {
hrefResolved = true;
href = blob;
} else {
// Needed for multiple levels of KML files in a KMZ
var tmpHref = getAbsoluteUri(href, sourceUri);
blob = uriResolver[tmpHref];
if (defined(blob)) {
hrefResolved = true;
href = blob;
}
}
}
if (!hrefResolved && defined(sourceUri)) {
href = getAbsoluteUri(href, getAbsoluteUri(sourceUri));
href = proxyUrl(href, proxy);
}
return href;
}
var colorOptions = {};
function parseColorString(value, isRandom) {
if (!defined(value)) {
return undefined;
}
if (value[0] === '#') {
value = value.substring(1);
}
var alpha = parseInt(value.substring(0, 2), 16) / 255.0;
var blue = parseInt(value.substring(2, 4), 16) / 255.0;
var green = parseInt(value.substring(4, 6), 16) / 255.0;
var red = parseInt(value.substring(6, 8), 16) / 255.0;
if (!isRandom) {
return new Color(red, green, blue, alpha);
}
if (red > 0) {
colorOptions.maximumRed = red;
} else {
colorOptions.red = 0;
}
if (green > 0) {
colorOptions.maximumGreen = green;
} else {
colorOptions.green = 0;
}
if (blue > 0) {
colorOptions.maximumBlue = blue;
} else {
colorOptions.blue = 0;
}
colorOptions.alpha = alpha;
return Color.fromRandom(colorOptions);
}
function queryColorValue(node, tagName, namespace) {
var value = queryStringValue(node, tagName, namespace);
if (!defined(value)) {
return undefined;
}
return parseColorString(value, queryStringValue(node, 'colorMode', namespace) === 'random');
}
function processTimeStamp(featureNode) {
var node = queryFirstNode(featureNode, 'TimeStamp', namespaces.kmlgx);
var whenString = queryStringValue(node, 'when', namespaces.kmlgx);
if (!defined(node) || !defined(whenString) || whenString.length === 0) {
return undefined;
}
//According to the KML spec, a TimeStamp represents a "single moment in time"
//However, since Cesium animates much differently than Google Earth, that doesn't
//Make much sense here. Instead, we use the TimeStamp as the moment the feature
//comes into existence. This works much better and gives a similar feel to
//GE's experience.
var when = JulianDate.fromIso8601(whenString);
var result = new TimeIntervalCollection();
result.addInterval(new TimeInterval({
start : when,
stop : Iso8601.MAXIMUM_VALUE
}));
return result;
}
function processTimeSpan(featureNode) {
var node = queryFirstNode(featureNode, 'TimeSpan', namespaces.kmlgx);
if (!defined(node)) {
return undefined;
}
var result;
var beginNode = queryFirstNode(node, 'begin', namespaces.kmlgx);
var beginDate = defined(beginNode) ? JulianDate.fromIso8601(beginNode.textContent) : undefined;
var endNode = queryFirstNode(node, 'end', namespaces.kmlgx);
var endDate = defined(endNode) ? JulianDate.fromIso8601(endNode.textContent) : undefined;
if (defined(beginDate) && defined(endDate)) {
if (JulianDate.lessThan(endDate, beginDate)) {
var tmp = beginDate;
beginDate = endDate;
endDate = tmp;
}
result = new TimeIntervalCollection();
result.addInterval(new TimeInterval({
start : beginDate,
stop : endDate
}));
} else if (defined(beginDate)) {
result = new TimeIntervalCollection();
result.addInterval(new TimeInterval({
start : beginDate,
stop : Iso8601.MAXIMUM_VALUE
}));
} else if (defined(endDate)) {
result = new TimeIntervalCollection();
result.addInterval(new TimeInterval({
start : Iso8601.MINIMUM_VALUE,
stop : endDate
}));
}
return result;
}
function createDefaultBillboard() {
var billboard = new BillboardGraphics();
billboard.width = BILLBOARD_SIZE;
billboard.height = BILLBOARD_SIZE;
billboard.scaleByDistance = new NearFarScalar(BILLBOARD_NEAR_DISTANCE, BILLBOARD_NEAR_RATIO, BILLBOARD_FAR_DISTANCE, BILLBOARD_FAR_RATIO);
billboard.pixelOffsetScaleByDistance = new NearFarScalar(BILLBOARD_NEAR_DISTANCE, BILLBOARD_NEAR_RATIO, BILLBOARD_FAR_DISTANCE, BILLBOARD_FAR_RATIO);
return billboard;
}
function createDefaultPolygon() {
var polygon = new PolygonGraphics();
polygon.outline = true;
polygon.outlineColor = Color.WHITE;
return polygon;
}
function createDefaultLabel() {
var label = new LabelGraphics();
label.translucencyByDistance = new NearFarScalar(3000000, 1.0, 5000000, 0.0);
label.pixelOffset = new Cartesian2(17, 0);
label.horizontalOrigin = HorizontalOrigin.LEFT;
label.font = '16px sans-serif';
label.style = LabelStyle.FILL_AND_OUTLINE;
return label;
}
function getIconHref(iconNode, dataSource, sourceUri, uriResolver, canRefresh) {
var href = queryStringValue(iconNode, 'href', namespaces.kml);
if (!defined(href) || (href.length === 0)) {
return undefined;
}
if (href.indexOf('root://icons/palette-') === 0) {
var palette = href.charAt(21);
// Get the icon number
var x = defaultValue(queryNumericValue(iconNode, 'x', namespaces.gx), 0);
var y = defaultValue(queryNumericValue(iconNode, 'y', namespaces.gx), 0);
x = Math.min(x / 32, 7);
y = 7 - Math.min(y / 32, 7);
var iconNum = (8 * y) + x;
href = 'https://maps.google.com/mapfiles/kml/pal' + palette + '/icon' + iconNum + '.png';
}
href = resolveHref(href, dataSource._proxy, sourceUri, uriResolver);
if (canRefresh) {
var refreshMode = queryStringValue(iconNode, 'refreshMode', namespaces.kml);
var viewRefreshMode = queryStringValue(iconNode, 'viewRefreshMode', namespaces.kml);
if (refreshMode === 'onInterval' || refreshMode === 'onExpire') {
console.log('KML - Unsupported Icon refreshMode: ' + refreshMode);
} else if (viewRefreshMode === 'onStop' || viewRefreshMode === 'onRegion') {
console.log('KML - Unsupported Icon viewRefreshMode: ' + viewRefreshMode);
}
var viewBoundScale = defaultValue(queryStringValue(iconNode, 'viewBoundScale', namespaces.kml), 1.0);
var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : '';
var viewFormat = defaultValue(queryStringValue(iconNode, 'viewFormat', namespaces.kml), defaultViewFormat);
var httpQuery = queryStringValue(iconNode, 'httpQuery', namespaces.kml);
var queryString = makeQueryString(viewFormat, httpQuery);
var icon = joinUrls(href, queryString, false);
return processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, icon, viewBoundScale, dataSource._lastCameraView.bbox);
}
return href;
}
function processBillboardIcon(dataSource, node, targetEntity, sourceUri, uriResolver) {
var scale = queryNumericValue(node, 'scale', namespaces.kml);
var heading = queryNumericValue(node, 'heading', namespaces.kml);
var color = queryColorValue(node, 'color', namespaces.kml);
var iconNode = queryFirstNode(node, 'Icon', namespaces.kml);
var icon = getIconHref(iconNode, dataSource, sourceUri, uriResolver, false);
var x = queryNumericValue(iconNode, 'x', namespaces.gx);
var y = queryNumericValue(iconNode, 'y', namespaces.gx);
var w = queryNumericValue(iconNode, 'w', namespaces.gx);
var h = queryNumericValue(iconNode, 'h', namespaces.gx);
var hotSpotNode = queryFirstNode(node, 'hotSpot', namespaces.kml);
var hotSpotX = queryNumericAttribute(hotSpotNode, 'x');
var hotSpotY = queryNumericAttribute(hotSpotNode, 'y');
var hotSpotXUnit = queryStringAttribute(hotSpotNode, 'xunits');
var hotSpotYUnit = queryStringAttribute(hotSpotNode, 'yunits');
var billboard = targetEntity.billboard;
if (!defined(billboard)) {
billboard = createDefaultBillboard();
targetEntity.billboard = billboard;
}
billboard.image = icon;
billboard.scale = scale;
billboard.color = color;
if (defined(x) || defined(y) || defined(w) || defined(h)) {
billboard.imageSubRegion = new BoundingRectangle(x, y, w, h);
}
//GE treats a heading of zero as no heading
//You can still point north using a 360 degree angle (or any multiple of 360)
if (defined(heading) && heading !== 0) {
billboard.rotation = CesiumMath.toRadians(-heading);
billboard.alignedAxis = Cartesian3.UNIT_Z;
}
//Hotpot is the KML equivalent of pixel offset
//The hotspot origin is the lower left, but we leave
//our billboard origin at the center and simply
//modify the pixel offset to take this into account
scale = defaultValue(scale, 1.0);
var xOffset;
var yOffset;
if (defined(hotSpotX)) {
if (hotSpotXUnit === 'pixels') {
xOffset = -hotSpotX * scale;
} else if (hotSpotXUnit === 'insetPixels') {
xOffset = (hotSpotX - BILLBOARD_SIZE) * scale;
} else if (hotSpotXUnit === 'fraction') {
xOffset = -hotSpotX * BILLBOARD_SIZE * scale;
}
xOffset += BILLBOARD_SIZE * 0.5 * scale;
}
if (defined(hotSpotY)) {
if (hotSpotYUnit === 'pixels') {
yOffset = hotSpotY * scale;
} else if (hotSpotYUnit === 'insetPixels') {
yOffset = (-hotSpotY + BILLBOARD_SIZE) * scale;
} else if (hotSpotYUnit === 'fraction') {
yOffset = hotSpotY * BILLBOARD_SIZE * scale;
}
yOffset -= BILLBOARD_SIZE * 0.5 * scale;
}
if (defined(xOffset) || defined(yOffset)) {
billboard.pixelOffset = new Cartesian2(xOffset, yOffset);
}
}
function applyStyle(dataSource, styleNode, targetEntity, sourceUri, uriResolver) {
for (var i = 0, len = styleNode.childNodes.length; i < len; i++) {
var node = styleNode.childNodes.item(i);
if (node.localName === 'IconStyle') {
processBillboardIcon(dataSource, node, targetEntity, sourceUri, uriResolver);
} else if (node.localName === 'LabelStyle') {
var label = targetEntity.label;
if (!defined(label)) {
label = createDefaultLabel();
targetEntity.label = label;
}
label.scale = defaultValue(queryNumericValue(node, 'scale', namespaces.kml), label.scale);
label.fillColor = defaultValue(queryColorValue(node, 'color', namespaces.kml), label.fillColor);
label.text = targetEntity.name;
} else if (node.localName === 'LineStyle') {
var polyline = targetEntity.polyline;
if (!defined(polyline)) {
polyline = new PolylineGraphics();
targetEntity.polyline = polyline;
}
polyline.width = queryNumericValue(node, 'width', namespaces.kml);
polyline.material = queryColorValue(node, 'color', namespaces.kml);
if (defined(queryColorValue(node, 'outerColor', namespaces.gx))) {
console.log('KML - gx:outerColor is not supported in a LineStyle');
}
if (defined(queryNumericValue(node, 'outerWidth', namespaces.gx))) {
console.log('KML - gx:outerWidth is not supported in a LineStyle');
}
if (defined(queryNumericValue(node, 'physicalWidth', namespaces.gx))) {
console.log('KML - gx:physicalWidth is not supported in a LineStyle');
}
if (defined(queryBooleanValue(node, 'labelVisibility', namespaces.gx))) {
console.log('KML - gx:labelVisibility is not supported in a LineStyle');
}
} else if (node.localName === 'PolyStyle') {
var polygon = targetEntity.polygon;
if (!defined(polygon)) {
polygon = createDefaultPolygon();
targetEntity.polygon = polygon;
}
polygon.material = defaultValue(queryColorValue(node, 'color', namespaces.kml), polygon.material);
polygon.fill = defaultValue(queryBooleanValue(node, 'fill', namespaces.kml), polygon.fill);
polygon.outline = defaultValue(queryBooleanValue(node, 'outline', namespaces.kml), polygon.outline);
} else if (node.localName === 'BalloonStyle') {
var bgColor = defaultValue(parseColorString(queryStringValue(node, 'bgColor', namespaces.kml)), Color.WHITE);
var textColor = defaultValue(parseColorString(queryStringValue(node, 'textColor', namespaces.kml)), Color.BLACK);
var text = queryStringValue(node, 'text', namespaces.kml);
//This is purely an internal property used in style processing,
//it never ends up on the final entity.
targetEntity.addProperty('balloonStyle');
targetEntity.balloonStyle = {
bgColor : bgColor,
textColor : textColor,
text : text
};
} else if (node.localName === 'ListStyle') {
var listItemType = queryStringValue(node, 'listItemType', namespaces.kml);
if (listItemType === 'radioFolder' || listItemType === 'checkOffOnly') {
console.log('KML - Unsupported ListStyle with listItemType: ' + listItemType);
}
}
}
}
//Processes and merges any inline styles for the provided node into the provided entity.
function computeFinalStyle(entity, dataSource, placeMark, styleCollection, sourceUri, uriResolver) {
var result = new Entity();
var styleEntity;
//Google earth seems to always use the last inline Style/StyleMap only
var styleIndex = -1;
var childNodes = placeMark.childNodes;
var length = childNodes.length;
for (var q = 0; q < length; q++) {
var child = childNodes[q];
if (child.localName === 'Style' || child.localName === 'StyleMap') {
styleIndex = q;
}
}
if (styleIndex !== -1) {
var inlineStyleNode = childNodes[styleIndex];
if (inlineStyleNode.localName === 'Style') {
applyStyle(dataSource, inlineStyleNode, result, sourceUri, uriResolver);
} else { // StyleMap
var pairs = queryChildNodes(inlineStyleNode, 'Pair', namespaces.kml);
for (var p = 0; p < pairs.length; p++) {
var pair = pairs[p];
var key = queryStringValue(pair, 'key', namespaces.kml);
if (key === 'normal') {
var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml);
if (defined(styleUrl)) {
styleEntity = styleCollection.getById(styleUrl);
if (!defined(styleEntity)) {
styleEntity = styleCollection.getById('#' + styleUrl);
}
if (defined(styleEntity)) {
result.merge(styleEntity);
}
} else {
var node = queryFirstNode(pair, 'Style', namespaces.kml);
applyStyle(dataSource, node, result, sourceUri, uriResolver);
}
} else {
console.log('KML - Unsupported StyleMap key: ' + key);
}
}
}
}
//Google earth seems to always use the first external style only.
var externalStyle = queryStringValue(placeMark, 'styleUrl', namespaces.kml);
if (defined(externalStyle)) {
var id = externalStyle;
if (externalStyle[0] !== '#' && externalStyle.indexOf('#') !== -1) {
var tokens = externalStyle.split('#');
var uri = tokens[0];
if (defined(sourceUri)) {
uri = getAbsoluteUri(uri, getAbsoluteUri(sourceUri));
}
id = uri + '#' + tokens[1];
}
styleEntity = styleCollection.getById(id);
if (!defined(styleEntity)) {
styleEntity = styleCollection.getById('#' + id);
}
if (defined(styleEntity)) {
result.merge(styleEntity);
}
}
return result;
}
//Asynchronously processes an external style file.
function processExternalStyles(dataSource, uri, styleCollection) {
return loadXML(proxyUrl(uri, dataSource._proxy)).then(function(styleKml) {
return processStyles(dataSource, styleKml, styleCollection, uri, true);
});
}
//Processes all shared and external styles and stores
//their id into the provided styleCollection.
//Returns an array of promises that will resolve when
//each style is loaded.
function processStyles(dataSource, kml, styleCollection, sourceUri, isExternal, uriResolver) {
var i;
var id;
var styleEntity;
var node;
var styleNodes = queryNodes(kml, 'Style', namespaces.kml);
if (defined(styleNodes)) {
var styleNodesLength = styleNodes.length;
for (i = 0; i < styleNodesLength; i++) {
node = styleNodes[i];
id = queryStringAttribute(node, 'id');
if (defined(id)) {
id = '#' + id;
if (isExternal && defined(sourceUri)) {
id = sourceUri + id;
}
if (!defined(styleCollection.getById(id))) {
styleEntity = new Entity({
id : id
});
styleCollection.add(styleEntity);
applyStyle(dataSource, node, styleEntity, sourceUri, uriResolver);
}
}
}
}
var styleMaps = queryNodes(kml, 'StyleMap', namespaces.kml);
if (defined(styleMaps)) {
var styleMapsLength = styleMaps.length;
for (i = 0; i < styleMapsLength; i++) {
var styleMap = styleMaps[i];
id = queryStringAttribute(styleMap, 'id');
if (defined(id)) {
var pairs = queryChildNodes(styleMap, 'Pair', namespaces.kml);
for (var p = 0; p < pairs.length; p++) {
var pair = pairs[p];
var key = queryStringValue(pair, 'key', namespaces.kml);
if (key === 'normal') {
id = '#' + id;
if (isExternal && defined(sourceUri)) {
id = sourceUri + id;
}
if (!defined(styleCollection.getById(id))) {
styleEntity = styleCollection.getOrCreateEntity(id);
var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml);
if (defined(styleUrl)) {
if (styleUrl[0] !== '#') {
styleUrl = '#' + styleUrl;
}
if (isExternal && defined(sourceUri)) {
styleUrl = sourceUri + styleUrl;
}
var base = styleCollection.getById(styleUrl);
if (defined(base)) {
styleEntity.merge(base);
}
} else {
node = queryFirstNode(pair, 'Style', namespaces.kml);
applyStyle(dataSource, node, styleEntity, sourceUri, uriResolver);
}
}
} else {
console.log('KML - Unsupported StyleMap key: ' + key);
}
}
}
}
}
var externalStyleHash = {};
var promises = [];
var styleUrlNodes = kml.getElementsByTagName('styleUrl');
var styleUrlNodesLength = styleUrlNodes.length;
for (i = 0; i < styleUrlNodesLength; i++) {
var styleReference = styleUrlNodes[i].textContent;
if (styleReference[0] !== '#') {
//According to the spec, all local styles should start with a #
//and everything else is an external style that has a # seperating
//the URL of the document and the style. However, Google Earth
//also accepts styleUrls without a # as meaning a local style.
var tokens = styleReference.split('#');
if (tokens.length === 2) {
var uri = tokens[0];
if (!defined(externalStyleHash[uri])) {
if (defined(sourceUri)) {
uri = getAbsoluteUri(uri, getAbsoluteUri(sourceUri));
}
promises.push(processExternalStyles(dataSource, uri, styleCollection, sourceUri));
}
}
}
}
return promises;
}
function createDropLine(entityCollection, entity, styleEntity) {
var entityPosition = new ReferenceProperty(entityCollection, entity.id, ['position']);
var surfacePosition = new ScaledPositionProperty(entity.position);
entity.polyline = defined(styleEntity.polyline) ? styleEntity.polyline.clone() : new PolylineGraphics();
entity.polyline.positions = new PositionPropertyArray([entityPosition, surfacePosition]);
}
function heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode) {
if (!defined(altitudeMode) && !defined(gxAltitudeMode) || altitudeMode === 'clampToGround') {
return HeightReference.CLAMP_TO_GROUND;
}
if (altitudeMode === 'relativeToGround') {
return HeightReference.RELATIVE_TO_GROUND;
}
if (altitudeMode === 'absolute') {
return HeightReference.NONE;
}
if (gxAltitudeMode === 'clampToSeaFloor') {
console.log('KML - :clampToSeaFloor is currently not supported, using :clampToGround.');
return HeightReference.CLAMP_TO_GROUND;
}
if (gxAltitudeMode === 'relativeToSeaFloor') {
console.log('KML - :relativeToSeaFloor is currently not supported, using :relativeToGround.');
return HeightReference.RELATIVE_TO_GROUND;
}
if (defined(altitudeMode)) {
console.log('KML - Unknown :' + altitudeMode + ', using :CLAMP_TO_GROUND.');
} else {
console.log('KML - Unknown :' + gxAltitudeMode + ', using :CLAMP_TO_GROUND.');
}
// Clamp to ground is the default
return HeightReference.CLAMP_TO_GROUND;
}
function createPositionPropertyFromAltitudeMode(property, altitudeMode, gxAltitudeMode) {
if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') {
//Just return the ellipsoid referenced property until we support MSL
return property;
}
if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || //
(defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) {
console.log('KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode));
}
// Clamp to ground is the default
return new ScaledPositionProperty(property);
}
function createPositionPropertyArrayFromAltitudeMode(properties, altitudeMode, gxAltitudeMode) {
if (!defined(properties)) {
return undefined;
}
if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') {
//Just return the ellipsoid referenced property until we support MSL
return properties;
}
if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || //
(defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) {
console.log('KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode));
}
// Clamp to ground is the default
var propertiesLength = properties.length;
for (var i = 0; i < propertiesLength; i++) {
var property = properties[i];
Ellipsoid.WGS84.scaleToGeodeticSurface(property, property);
}
return properties;
}
function processPositionGraphics(dataSource, entity, styleEntity, heightReference) {
var label = entity.label;
if (!defined(label)) {
label = defined(styleEntity.label) ? styleEntity.label.clone() : createDefaultLabel();
entity.label = label;
}
label.text = entity.name;
var billboard = entity.billboard;
if (!defined(billboard)) {
billboard = defined(styleEntity.billboard) ? styleEntity.billboard.clone() : createDefaultBillboard();
entity.billboard = billboard;
}
if (!defined(billboard.image)) {
billboard.image = dataSource._pinBuilder.fromColor(Color.YELLOW, 64);
}
var scale = 1.0;
if (defined(billboard.scale)) {
scale = billboard.scale.getValue();
if (scale !== 0) {
label.pixelOffset = new Cartesian2((scale * 16) + 1, 0);
} else {
//Minor tweaks to better match Google Earth.
label.pixelOffset = undefined;
label.horizontalOrigin = undefined;
}
}
if (defined(heightReference) && dataSource._clampToGround) {
billboard.heightReference = heightReference;
label.heightReference = heightReference;
}
}
function processPathGraphics(dataSource, entity, styleEntity) {
var path = entity.path;
if (!defined(path)) {
path = new PathGraphics();
path.leadTime = 0;
entity.path = path;
}
var polyline = styleEntity.polyline;
if (defined(polyline)) {
path.material = polyline.material;
path.width = polyline.width;
}
}
function processPoint(dataSource, entityCollection, geometryNode, entity, styleEntity) {
var coordinatesString = queryStringValue(geometryNode, 'coordinates', namespaces.kml);
var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
var position = readCoordinate(coordinatesString);
entity.position = position;
processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode));
if (extrude && isExtrudable(altitudeMode, gxAltitudeMode)) {
createDropLine(entityCollection, entity, styleEntity);
}
return true;
}
function processLineStringOrLinearRing(dataSource, entityCollection, geometryNode, entity, styleEntity) {
var coordinatesNode = queryFirstNode(geometryNode, 'coordinates', namespaces.kml);
var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
var tessellate = queryBooleanValue(geometryNode, 'tessellate', namespaces.kml);
var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
if (defined(queryNumericValue(geometryNode, 'drawOrder', namespaces.gx))) {
console.log('KML - gx:drawOrder is not supported in LineStrings');
}
var coordinates = readCoordinates(coordinatesNode);
var polyline = styleEntity.polyline;
if (canExtrude && extrude) {
var wall = new WallGraphics();
entity.wall = wall;
wall.positions = coordinates;
var polygon = styleEntity.polygon;
if (defined(polygon)) {
wall.fill = polygon.fill;
wall.outline = polygon.outline;
wall.material = polygon.material;
}
if (defined(polyline)) {
wall.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE;
wall.outlineWidth = polyline.width;
}
} else {
if (dataSource._clampToGround && !canExtrude && tessellate) {
var corridor = new CorridorGraphics();
entity.corridor = corridor;
corridor.positions = coordinates;
if (defined(polyline)) {
corridor.material = defined(polyline.material) ? polyline.material.color.getValue(Iso8601.MINIMUM_VALUE) : Color.WHITE;
corridor.width = defaultValue(polyline.width, 1.0);
} else {
corridor.material = Color.WHITE;
corridor.width = 1.0;
}
} else {
polyline = defined(polyline) ? polyline.clone() : new PolylineGraphics();
entity.polyline = polyline;
polyline.positions = createPositionPropertyArrayFromAltitudeMode(coordinates, altitudeMode, gxAltitudeMode);
if (!tessellate || canExtrude) {
polyline.followSurface = false;
}
}
}
return true;
}
function processPolygon(dataSource, entityCollection, geometryNode, entity, styleEntity) {
var outerBoundaryIsNode = queryFirstNode(geometryNode, 'outerBoundaryIs', namespaces.kml);
var linearRingNode = queryFirstNode(outerBoundaryIsNode, 'LinearRing', namespaces.kml);
var coordinatesNode = queryFirstNode(linearRingNode, 'coordinates', namespaces.kml);
var coordinates = readCoordinates(coordinatesNode);
var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
var polygon = defined(styleEntity.polygon) ? styleEntity.polygon.clone() : createDefaultPolygon();
var polyline = styleEntity.polyline;
if (defined(polyline)) {
polygon.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE;
polygon.outlineWidth = polyline.width;
}
entity.polygon = polygon;
if (canExtrude) {
polygon.perPositionHeight = true;
polygon.extrudedHeight = extrude ? 0 : undefined;
} else if (!dataSource._clampToGround) {
polygon.height = 0;
}
if (defined(coordinates)) {
var hierarchy = new PolygonHierarchy(coordinates);
var innerBoundaryIsNodes = queryChildNodes(geometryNode, 'innerBoundaryIs', namespaces.kml);
for (var j = 0; j < innerBoundaryIsNodes.length; j++) {
linearRingNode = queryChildNodes(innerBoundaryIsNodes[j], 'LinearRing', namespaces.kml);
for (var k = 0; k < linearRingNode.length; k++) {
coordinatesNode = queryFirstNode(linearRingNode[k], 'coordinates', namespaces.kml);
coordinates = readCoordinates(coordinatesNode);
if (defined(coordinates)) {
hierarchy.holes.push(new PolygonHierarchy(coordinates));
}
}
}
polygon.hierarchy = hierarchy;
}
return true;
}
function processTrack(dataSource, entityCollection, geometryNode, entity, styleEntity) {
var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
var coordNodes = queryChildNodes(geometryNode, 'coord', namespaces.gx);
var angleNodes = queryChildNodes(geometryNode, 'angles', namespaces.gx);
var timeNodes = queryChildNodes(geometryNode, 'when', namespaces.kml);
var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
if (angleNodes.length > 0) {
console.log('KML - gx:angles are not supported in gx:Tracks');
}
var length = Math.min(coordNodes.length, timeNodes.length);
var coordinates = [];
var times = [];
for (var i = 0; i < length; i++) {
var position = readCoordinate(coordNodes[i].textContent);
coordinates.push(position);
times.push(JulianDate.fromIso8601(timeNodes[i].textContent));
}
var property = new SampledPositionProperty();
property.addSamples(times, coordinates);
entity.position = property;
processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode));
processPathGraphics(dataSource, entity, styleEntity);
entity.availability = new TimeIntervalCollection();
if (timeNodes.length > 0) {
entity.availability.addInterval(new TimeInterval({
start : times[0],
stop : times[times.length - 1]
}));
}
if (canExtrude && extrude) {
createDropLine(entityCollection, entity, styleEntity);
}
return true;
}
function addToMultiTrack(times, positions, composite, availability, dropShowProperty, extrude, altitudeMode, gxAltitudeMode, includeEndPoints) {
var start = times[0];
var stop = times[times.length - 1];
var data = new SampledPositionProperty();
data.addSamples(times, positions);
composite.intervals.addInterval(new TimeInterval({
start : start,
stop : stop,
isStartIncluded : includeEndPoints,
isStopIncluded : includeEndPoints,
data : createPositionPropertyFromAltitudeMode(data, altitudeMode, gxAltitudeMode)
}));
availability.addInterval(new TimeInterval({
start : start,
stop : stop,
isStartIncluded : includeEndPoints,
isStopIncluded : includeEndPoints
}));
dropShowProperty.intervals.addInterval(new TimeInterval({
start : start,
stop : stop,
isStartIncluded : includeEndPoints,
isStopIncluded : includeEndPoints,
data : extrude
}));
}
function processMultiTrack(dataSource, entityCollection, geometryNode, entity, styleEntity) {
// Multitrack options do not work in GE as detailed in the spec,
// rather than altitudeMode being at the MultiTrack level,
// GE just defers all settings to the underlying track.
var interpolate = queryBooleanValue(geometryNode, 'interpolate', namespaces.gx);
var trackNodes = queryChildNodes(geometryNode, 'Track', namespaces.gx);
var times;
var lastStop;
var lastStopPosition;
var needDropLine = false;
var dropShowProperty = new TimeIntervalCollectionProperty();
var availability = new TimeIntervalCollection();
var composite = new CompositePositionProperty();
for (var i = 0, len = trackNodes.length; i < len; i++) {
var trackNode = trackNodes[i];
var timeNodes = queryChildNodes(trackNode, 'when', namespaces.kml);
var coordNodes = queryChildNodes(trackNode, 'coord', namespaces.gx);
var altitudeMode = queryStringValue(trackNode, 'altitudeMode', namespaces.kml);
var gxAltitudeMode = queryStringValue(trackNode, 'altitudeMode', namespaces.gx);
var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
var extrude = queryBooleanValue(trackNode, 'extrude', namespaces.kml);
var length = Math.min(coordNodes.length, timeNodes.length);
var positions = [];
times = [];
for (var x = 0; x < length; x++) {
var position = readCoordinate(coordNodes[x].textContent);
positions.push(position);
times.push(JulianDate.fromIso8601(timeNodes[x].textContent));
}
if (interpolate) {
//If we are interpolating, then we need to fill in the end of
//the last track and the beginning of this one with a sampled
//property. From testing in Google Earth, this property
//is never extruded and always absolute.
if (defined(lastStop)) {
addToMultiTrack([lastStop, times[0]], [lastStopPosition, positions[0]], composite, availability, dropShowProperty, false, 'absolute', undefined, false);
}
lastStop = times[length - 1];
lastStopPosition = positions[positions.length - 1];
}
addToMultiTrack(times, positions, composite, availability, dropShowProperty, canExtrude && extrude, altitudeMode, gxAltitudeMode, true);
needDropLine = needDropLine || (canExtrude && extrude);
}
entity.availability = availability;
entity.position = composite;
processPositionGraphics(dataSource, entity, styleEntity);
processPathGraphics(dataSource, entity, styleEntity);
if (needDropLine) {
createDropLine(entityCollection, entity, styleEntity);
entity.polyline.show = dropShowProperty;
}
return true;
}
function processMultiGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity, context) {
var childNodes = geometryNode.childNodes;
var hasGeometry = false;
for (var i = 0, len = childNodes.length; i < len; i++) {
var childNode = childNodes.item(i);
var geometryProcessor = geometryTypes[childNode.localName];
if (defined(geometryProcessor)) {
var childEntity = createEntity(childNode, entityCollection, context);
childEntity.parent = entity;
childEntity.name = entity.name;
childEntity.availability = entity.availability;
childEntity.description = entity.description;
childEntity.kml = entity.kml;
if (geometryProcessor(dataSource, entityCollection, childNode, childEntity, styleEntity)) {
hasGeometry = true;
}
}
}
return hasGeometry;
}
function processUnsupportedGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity) {
console.log('KML - Unsupported geometry: ' + geometryNode.localName);
return false;
}
function processExtendedData(node, entity) {
var extendedDataNode = queryFirstNode(node, 'ExtendedData', namespaces.kml);
if (!defined(extendedDataNode)) {
return undefined;
}
if (defined(queryFirstNode(extendedDataNode, 'SchemaData', namespaces.kml))) {
console.log('KML - SchemaData is unsupported');
}
if (defined(queryStringAttribute(extendedDataNode, 'xmlns:prefix'))) {
console.log('KML - ExtendedData with xmlns:prefix is unsupported');
}
var result = {};
var dataNodes = queryChildNodes(extendedDataNode, 'Data', namespaces.kml);
if (defined(dataNodes)) {
var length = dataNodes.length;
for (var i = 0; i < length; i++) {
var dataNode = dataNodes[i];
var name = queryStringAttribute(dataNode, 'name');
if (defined(name)) {
result[name] = {
displayName : queryStringValue(dataNode, 'displayName', namespaces.kml),
value : queryStringValue(dataNode, 'value', namespaces.kml)
};
}
}
}
entity.kml.extendedData = result;
}
var scratchDiv = document.createElement('div');
function processDescription(node, entity, styleEntity, uriResolver) {
var i;
var key;
var keys;
var kmlData = entity.kml;
var extendedData = kmlData.extendedData;
var description = queryStringValue(node, 'description', namespaces.kml);
var balloonStyle = defaultValue(entity.balloonStyle, styleEntity.balloonStyle);
var background = Color.WHITE;
var foreground = Color.BLACK;
var text = description;
if (defined(balloonStyle)) {
background = defaultValue(balloonStyle.bgColor, Color.WHITE);
foreground = defaultValue(balloonStyle.textColor, Color.BLACK);
text = defaultValue(balloonStyle.text, description);
}
var value;
if (defined(text)) {
text = text.replace('$[name]', defaultValue(entity.name, ''));
text = text.replace('$[description]', defaultValue(description, ''));
text = text.replace('$[address]', defaultValue(kmlData.address, ''));
text = text.replace('$[Snippet]', defaultValue(kmlData.snippet, ''));
text = text.replace('$[id]', entity.id);
//While not explicitly defined by the OGC spec, in Google Earth
//The appearance of geDirections adds the directions to/from links
//We simply replace this string with nothing.
text = text.replace('$[geDirections]', '');
if (defined(extendedData)) {
var matches = text.match(/\$\[.+?\]/g);
if (matches !== null) {
for (i = 0; i < matches.length; i++) {
var token = matches[i];
var propertyName = token.substr(2, token.length - 3);
var isDisplayName = /\/displayName$/.test(propertyName);
propertyName = propertyName.replace(/\/displayName$/, '');
value = extendedData[propertyName];
if (defined(value)) {
value = isDisplayName ? value.displayName : value.value;
}
if (defined(value)) {
text = text.replace(token, defaultValue(value, ''));
}
}
}
}
} else if (defined(extendedData)) {
//If no description exists, build a table out of the extended data
keys = Object.keys(extendedData);
if (keys.length > 0) {
text = '';
for (i = 0; i < keys.length; i++) {
key = keys[i];
value = extendedData[key];
text += '' + defaultValue(value.displayName, key) + ' ' + defaultValue(value.value, '') + ' ';
}
text += '
';
}
}
if (!defined(text)) {
//No description
return;
}
//Turns non-explicit links into clickable links.
text = autolinker.link(text);
//Use a temporary div to manipulate the links
//so that they open in a new window.
scratchDiv.innerHTML = text;
var links = scratchDiv.querySelectorAll('a');
for (i = 0; i < links.length; i++) {
links[i].setAttribute('target', '_blank');
}
//Rewrite any KMZ embedded urls
if (defined(uriResolver) && uriResolver.keys.length > 1) {
replaceAttributes(scratchDiv, 'a', 'href', uriResolver);
replaceAttributes(scratchDiv, 'img', 'src', uriResolver);
}
var tmp = '';
tmp += scratchDiv.innerHTML + '
';
scratchDiv.innerHTML = '';
//Set the final HTML as the description.
entity.description = tmp;
}
function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var entity = createEntity(featureNode, entityCollection, context);
var kmlData = entity.kml;
var styleEntity = computeFinalStyle(entity, dataSource, featureNode, styleCollection, sourceUri, uriResolver);
var name = queryStringValue(featureNode, 'name', namespaces.kml);
entity.name = name;
entity.parent = parent;
var availability = processTimeSpan(featureNode);
if (!defined(availability)) {
availability = processTimeStamp(featureNode);
}
entity.availability = availability;
mergeAvailabilityWithParent(entity);
// Per KML spec "A Feature is visible only if it and all its ancestors are visible."
function ancestryIsVisible(parentEntity) {
if (!parentEntity) {
return true;
}
return parentEntity.show && ancestryIsVisible(parentEntity.parent);
}
var visibility = queryBooleanValue(featureNode, 'visibility', namespaces.kml);
entity.show = ancestryIsVisible(parent) && defaultValue(visibility, true);
//var open = queryBooleanValue(featureNode, 'open', namespaces.kml);
var authorNode = queryFirstNode(featureNode, 'author', namespaces.atom);
var author = kmlData.author;
author.name = queryStringValue(authorNode, 'name', namespaces.atom);
author.uri = queryStringValue(authorNode, 'uri', namespaces.atom);
author.email = queryStringValue(authorNode, 'email', namespaces.atom);
var linkNode = queryFirstNode(featureNode, 'link', namespaces.atom);
var link = kmlData.link;
link.href = queryStringAttribute(linkNode, 'href');
link.hreflang = queryStringAttribute(linkNode, 'hreflang');
link.rel = queryStringAttribute(linkNode, 'rel');
link.type = queryStringAttribute(linkNode, 'type');
link.title = queryStringAttribute(linkNode, 'title');
link.length = queryStringAttribute(linkNode, 'length');
kmlData.address = queryStringValue(featureNode, 'address', namespaces.kml);
kmlData.phoneNumber = queryStringValue(featureNode, 'phoneNumber', namespaces.kml);
kmlData.snippet = queryStringValue(featureNode, 'Snippet', namespaces.kml);
processExtendedData(featureNode, entity);
processDescription(featureNode, entity, styleEntity, uriResolver);
if (defined(queryFirstNode(featureNode, 'Camera', namespaces.kml))) {
console.log('KML - Unsupported view: Camera');
}
if (defined(queryFirstNode(featureNode, 'LookAt', namespaces.kml))) {
console.log('KML - Unsupported view: LookAt');
}
if (defined(queryFirstNode(featureNode, 'Region', namespaces.kml))) {
console.log('KML - Placemark Regions are unsupported');
}
return {
entity : entity,
styleEntity : styleEntity
};
}
var geometryTypes = {
Point : processPoint,
LineString : processLineStringOrLinearRing,
LinearRing : processLineStringOrLinearRing,
Polygon : processPolygon,
Track : processTrack,
MultiTrack : processMultiTrack,
MultiGeometry : processMultiGeometry,
Model : processUnsupportedGeometry
};
function processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var featureTypeNames = Object.keys(featureTypes);
var featureTypeNamesLength = featureTypeNames.length;
for (var i = 0; i < featureTypeNamesLength; i++) {
var featureName = featureTypeNames[i];
var processFeatureNode = featureTypes[featureName];
var childNodes = node.childNodes;
var length = childNodes.length;
for (var q = 0; q < length; q++) {
var child = childNodes[q];
if (child.localName === featureName &&
((namespaces.kml.indexOf(child.namespaceURI) !== -1) || (namespaces.gx.indexOf(child.namespaceURI) !== -1))) {
processFeatureNode(dataSource, parent, child, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
}
}
}
}
function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
}
function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
var entity = r.entity;
var styleEntity = r.styleEntity;
var hasGeometry = false;
var childNodes = placemark.childNodes;
for (var i = 0, len = childNodes.length; i < len && !hasGeometry; i++) {
var childNode = childNodes.item(i);
var geometryProcessor = geometryTypes[childNode.localName];
if (defined(geometryProcessor)) {
// pass the placemark entity id as a context for case of defining multiple child entities together to handle case
// where some malformed kmls reuse the same id across placemarks, which works in GE, but is not technically to spec.
geometryProcessor(dataSource, entityCollection, childNode, entity, styleEntity, entity.id);
hasGeometry = true;
}
}
if (!hasGeometry) {
entity.merge(styleEntity);
processPositionGraphics(dataSource, entity, styleEntity);
}
}
function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
var entity = r.entity;
var geometry;
var isLatLonQuad = false;
var positions = readCoordinates(queryFirstNode(groundOverlay, 'LatLonQuad', namespaces.gx));
if (defined(positions)) {
geometry = createDefaultPolygon();
geometry.hierarchy = new PolygonHierarchy(positions);
entity.polygon = geometry;
isLatLonQuad = true;
} else {
geometry = new RectangleGraphics();
entity.rectangle = geometry;
var latLonBox = queryFirstNode(groundOverlay, 'LatLonBox', namespaces.kml);
if (defined(latLonBox)) {
var west = queryNumericValue(latLonBox, 'west', namespaces.kml);
var south = queryNumericValue(latLonBox, 'south', namespaces.kml);
var east = queryNumericValue(latLonBox, 'east', namespaces.kml);
var north = queryNumericValue(latLonBox, 'north', namespaces.kml);
if (defined(west)) {
west = CesiumMath.negativePiToPi(CesiumMath.toRadians(west));
}
if (defined(south)) {
south = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(south));
}
if (defined(east)) {
east = CesiumMath.negativePiToPi(CesiumMath.toRadians(east));
}
if (defined(north)) {
north = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(north));
}
geometry.coordinates = new Rectangle(west, south, east, north);
var rotation = queryNumericValue(latLonBox, 'rotation', namespaces.kml);
if (defined(rotation)) {
geometry.rotation = CesiumMath.toRadians(rotation);
}
}
}
var iconNode = queryFirstNode(groundOverlay, 'Icon', namespaces.kml);
var href = getIconHref(iconNode, dataSource, sourceUri, uriResolver, true);
if (defined(href)) {
if (isLatLonQuad) {
console.log('KML - gx:LatLonQuad Icon does not support texture projection.');
}
var x = queryNumericValue(iconNode, 'x', namespaces.gx);
var y = queryNumericValue(iconNode, 'y', namespaces.gx);
var w = queryNumericValue(iconNode, 'w', namespaces.gx);
var h = queryNumericValue(iconNode, 'h', namespaces.gx);
if (defined(x) || defined(y) || defined(w) || defined(h)) {
console.log('KML - gx:x, gx:y, gx:w, gx:h aren\'t supported for GroundOverlays');
}
geometry.material = href;
geometry.material.color = queryColorValue(groundOverlay, 'color', namespaces.kml);
geometry.material.transparent = true;
} else {
geometry.material = queryColorValue(groundOverlay, 'color', namespaces.kml);
}
var altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.kml);
if (defined(altitudeMode)) {
if (altitudeMode === 'absolute') {
//Use height above ellipsoid until we support MSL.
geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml);
} else if (altitudeMode !== 'clampToGround'){
console.log('KML - Unknown altitudeMode: ' + altitudeMode);
}
// else just use the default of 0 until we support 'clampToGround'
} else {
altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.gx);
if (altitudeMode === 'relativeToSeaFloor') {
console.log('KML - altitudeMode relativeToSeaFloor is currently not supported, treating as absolute.');
geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml);
} else if (altitudeMode === 'clampToSeaFloor') {
console.log('KML - altitudeMode clampToSeaFloor is currently not supported, treating as clampToGround.');
} else if (defined(altitudeMode)) {
console.log('KML - Unknown altitudeMode: ' + altitudeMode);
}
}
}
function processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
dataSource._unsupportedNode.raiseEvent(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver);
console.log('KML - Unsupported feature: ' + node.localName);
}
var RefreshMode = {
INTERVAL : 0,
EXPIRE : 1,
STOP : 2
};
function cleanupString(s) {
if (!defined(s) || s.length === 0) {
return '';
}
var sFirst = s[0];
if (sFirst === '&') {
s.splice(0, 1);
}
if(sFirst !== '?') {
s = '?' + s;
}
return s;
}
function makeQueryString(string1, string2) {
var result = '';
if ((defined(string1) && string1.length > 0) || (defined(string2) && string2.length > 0)) {
result += joinUrls(cleanupString(string1), cleanupString(string2), false);
}
return result;
}
var zeroRectangle = new Rectangle();
var scratchCartographic = new Cartographic();
var scratchCartesian2 = new Cartesian2();
var scratchCartesian3 = new Cartesian3();
function processNetworkLinkQueryString(camera, canvas, queryString, viewBoundScale, bbox) {
function fixLatitude(value) {
if (value < -CesiumMath.PI_OVER_TWO) {
return -CesiumMath.PI_OVER_TWO;
} else if (value > CesiumMath.PI_OVER_TWO) {
return CesiumMath.PI_OVER_TWO;
}
return value;
}
function fixLongitude(value) {
if (value > CesiumMath.PI) {
return value - CesiumMath.TWO_PI;
} else if (value < -CesiumMath.PI) {
return value + CesiumMath.TWO_PI;
}
return value;
}
if (defined(camera) && camera._mode !== SceneMode.MORPHING) {
var wgs84 = Ellipsoid.WGS84;
var centerCartesian;
var centerCartographic;
bbox = defaultValue(bbox, zeroRectangle);
if (defined(canvas)) {
scratchCartesian2.x = canvas.clientWidth * 0.5;
scratchCartesian2.y = canvas.clientHeight * 0.5;
centerCartesian = camera.pickEllipsoid(scratchCartesian2, wgs84, scratchCartesian3);
}
if (defined(centerCartesian)) {
centerCartographic = wgs84.cartesianToCartographic(centerCartesian, scratchCartographic);
} else {
centerCartographic = Rectangle.center(bbox, scratchCartographic);
centerCartesian = wgs84.cartographicToCartesian(centerCartographic);
}
if (defined(viewBoundScale) && !CesiumMath.equalsEpsilon(viewBoundScale, 1.0, CesiumMath.EPSILON9)) {
var newHalfWidth = bbox.width * viewBoundScale * 0.5;
var newHalfHeight = bbox.height * viewBoundScale * 0.5;
bbox = new Rectangle(fixLongitude(centerCartographic.longitude - newHalfWidth),
fixLatitude(centerCartographic.latitude - newHalfHeight),
fixLongitude(centerCartographic.longitude + newHalfWidth),
fixLatitude(centerCartographic.latitude + newHalfHeight)
);
}
queryString = queryString.replace('[bboxWest]', CesiumMath.toDegrees(bbox.west).toString());
queryString = queryString.replace('[bboxSouth]', CesiumMath.toDegrees(bbox.south).toString());
queryString = queryString.replace('[bboxEast]', CesiumMath.toDegrees(bbox.east).toString());
queryString = queryString.replace('[bboxNorth]', CesiumMath.toDegrees(bbox.north).toString());
var lon = CesiumMath.toDegrees(centerCartographic.longitude).toString();
var lat = CesiumMath.toDegrees(centerCartographic.latitude).toString();
queryString = queryString.replace('[lookatLon]', lon);
queryString = queryString.replace('[lookatLat]', lat);
queryString = queryString.replace('[lookatTilt]', CesiumMath.toDegrees(camera.pitch).toString());
queryString = queryString.replace('[lookatHeading]', CesiumMath.toDegrees(camera.heading).toString());
queryString = queryString.replace('[lookatRange]', Cartesian3.distance(camera.positionWC, centerCartesian));
queryString = queryString.replace('[lookatTerrainLon]', lon);
queryString = queryString.replace('[lookatTerrainLat]', lat);
queryString = queryString.replace('[lookatTerrainAlt]', centerCartographic.height.toString());
wgs84.cartesianToCartographic(camera.positionWC, scratchCartographic);
queryString = queryString.replace('[cameraLon]', CesiumMath.toDegrees(scratchCartographic.longitude).toString());
queryString = queryString.replace('[cameraLat]', CesiumMath.toDegrees(scratchCartographic.latitude).toString());
queryString = queryString.replace('[cameraAlt]', CesiumMath.toDegrees(scratchCartographic.height).toString());
var frustum = camera.frustum;
var aspectRatio = frustum.aspectRatio;
var horizFov = '';
var vertFov = '';
if (defined(aspectRatio)) {
var fov = CesiumMath.toDegrees(frustum.fov);
if (aspectRatio > 1.0) {
horizFov = fov;
vertFov = fov / aspectRatio;
} else {
vertFov = fov;
horizFov = fov * aspectRatio;
}
}
queryString = queryString.replace('[horizFov]', horizFov.toString());
queryString = queryString.replace('[vertFov]', vertFov.toString());
} else {
queryString = queryString.replace('[bboxWest]', '-180');
queryString = queryString.replace('[bboxSouth]', '-90');
queryString = queryString.replace('[bboxEast]', '180');
queryString = queryString.replace('[bboxNorth]', '90');
queryString = queryString.replace('[lookatLon]', '');
queryString = queryString.replace('[lookatLat]', '');
queryString = queryString.replace('[lookatRange]', '');
queryString = queryString.replace('[lookatTilt]', '');
queryString = queryString.replace('[lookatHeading]', '');
queryString = queryString.replace('[lookatTerrainLon]', '');
queryString = queryString.replace('[lookatTerrainLat]', '');
queryString = queryString.replace('[lookatTerrainAlt]', '');
queryString = queryString.replace('[cameraLon]', '');
queryString = queryString.replace('[cameraLat]', '');
queryString = queryString.replace('[cameraAlt]', '');
queryString = queryString.replace('[horizFov]', '');
queryString = queryString.replace('[vertFov]', '');
}
if (defined(canvas)) {
queryString = queryString.replace('[horizPixels]', canvas.clientWidth);
queryString = queryString.replace('[vertPixels]', canvas.clientHeight);
} else {
queryString = queryString.replace('[horizPixels]', '');
queryString = queryString.replace('[vertPixels]', '');
}
queryString = queryString.replace('[terrainEnabled]', '1');
queryString = queryString.replace('[clientVersion]', '1');
queryString = queryString.replace('[kmlVersion]', '2.2');
queryString = queryString.replace('[clientName]', 'Cesium');
queryString = queryString.replace('[language]', 'English');
return queryString;
}
function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
var networkEntity = r.entity;
var link = queryFirstNode(node, 'Link', namespaces.kml);
if(!defined(link)){
link = queryFirstNode(node, 'Url', namespaces.kml);
}
if (defined(link)) {
var href = queryStringValue(link, 'href', namespaces.kml);
if (defined(href)) {
var newSourceUri = href;
href = resolveHref(href, undefined, sourceUri, uriResolver);
var linkUrl;
// We need to pass in the original path if resolveHref returns a data uri because the network link
// references a document in a KMZ archive
if (/^data:/.test(href)) {
// No need to build a query string for a data uri, just use as is
linkUrl = href;
// So if sourceUri isn't the kmz file, then its another kml in the archive, so resolve it
if (!/\.kmz/i.test(sourceUri)) {
newSourceUri = getAbsoluteUri(newSourceUri, sourceUri);
}
} else {
newSourceUri = href; // Not a data uri so use the fully qualified uri
var viewRefreshMode = queryStringValue(link, 'viewRefreshMode', namespaces.kml);
var viewBoundScale = defaultValue(queryStringValue(link, 'viewBoundScale', namespaces.kml), 1.0);
var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : '';
var viewFormat = defaultValue(queryStringValue(link, 'viewFormat', namespaces.kml), defaultViewFormat);
var httpQuery = queryStringValue(link, 'httpQuery', namespaces.kml);
var queryString = makeQueryString(viewFormat, httpQuery);
linkUrl = processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, joinUrls(href, queryString, false),
viewBoundScale, dataSource._lastCameraView.bbox);
}
var options = {
sourceUri : newSourceUri,
uriResolver : uriResolver,
context : networkEntity.id
};
var networkLinkCollection = new EntityCollection();
var promise = load(dataSource, networkLinkCollection, linkUrl, options).then(function(rootElement) {
var entities = dataSource._entityCollection;
var newEntities = networkLinkCollection.values;
entities.suspendEvents();
for (var i = 0; i < newEntities.length; i++) {
var newEntity = newEntities[i];
if (!defined(newEntity.parent)) {
newEntity.parent = networkEntity;
mergeAvailabilityWithParent(newEntity);
}
entities.add(newEntity);
}
entities.resumeEvents();
// Add network links to a list if we need they will need to be updated
var refreshMode = queryStringValue(link, 'refreshMode', namespaces.kml);
var refreshInterval = defaultValue(queryNumericValue(link, 'refreshInterval', namespaces.kml), 0);
if ((refreshMode === 'onInterval' && refreshInterval > 0 ) || (refreshMode === 'onExpire') || (viewRefreshMode === 'onStop')) {
var networkLinkControl = queryFirstNode(rootElement, 'NetworkLinkControl', namespaces.kml);
var hasNetworkLinkControl = defined(networkLinkControl);
var now = JulianDate.now();
var networkLinkInfo = {
id : createGuid(),
href : href,
cookie : '',
queryString : queryString,
lastUpdated : now,
updating : false,
entity : networkEntity,
viewBoundScale : viewBoundScale,
needsUpdate : false,
cameraUpdateTime : now
};
var minRefreshPeriod = 0;
if (hasNetworkLinkControl) {
networkLinkInfo.cookie = defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), '');
minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0);
}
if (refreshMode === 'onInterval') {
if (hasNetworkLinkControl) {
refreshInterval = Math.max(minRefreshPeriod, refreshInterval);
}
networkLinkInfo.refreshMode = RefreshMode.INTERVAL;
networkLinkInfo.time = refreshInterval;
} else if (refreshMode === 'onExpire') {
var expires;
if (hasNetworkLinkControl) {
expires = queryStringValue(networkLinkControl, 'expires', namespaces.kml);
}
if (defined(expires)) {
try {
var date = JulianDate.fromIso8601(expires);
var diff = JulianDate.secondsDifference(date, now);
if (diff > 0 && diff < minRefreshPeriod) {
JulianDate.addSeconds(now, minRefreshPeriod, date);
}
networkLinkInfo.refreshMode = RefreshMode.EXPIRE;
networkLinkInfo.time = date;
} catch (e) {
console.log('KML - NetworkLinkControl expires is not a valid date');
}
} else {
console.log('KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element');
}
} else {
if (dataSource._camera) { // Only allow onStop refreshes if we have a camera
networkLinkInfo.refreshMode = RefreshMode.STOP;
networkLinkInfo.time = defaultValue(queryNumericValue(link, 'viewRefreshTime', namespaces.kml), 0);
} else {
console.log('A NetworkLink with viewRefreshMode=onStop requires a camera be passed in when creating the KmlDataSource');
}
}
if (defined(networkLinkInfo.refreshMode)) {
dataSource._networkLinks.set(networkLinkInfo.id, networkLinkInfo);
}
} else if (viewRefreshMode === 'onRegion'){
console.log('KML - Unsupported viewRefreshMode: onRegion');
}
});
promises.push(promise);
}
}
}
// Ensure Specs/Data/KML/unsupported.kml is kept up to date with these supported types
var featureTypes = {
Document : processDocument,
Folder : processFolder,
Placemark : processPlacemark,
NetworkLink : processNetworkLink,
GroundOverlay : processGroundOverlay,
PhotoOverlay : processUnsupportedFeature,
ScreenOverlay : processUnsupportedFeature,
Tour : processUnsupportedFeature
};
function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
var featureProcessor = featureTypes[node.localName];
if (defined(featureProcessor)) {
featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
} else {
processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
}
}
function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context) {
entityCollection.removeAll();
var promises = [];
var documentElement = kml.documentElement;
var document = documentElement.localName === 'Document' ? documentElement : queryFirstNode(documentElement, 'Document', namespaces.kml);
var name = queryStringValue(document, 'name', namespaces.kml);
if (!defined(name) && defined(sourceUri)) {
name = getFilenameFromUri(sourceUri);
}
// Only set the name from the root document
if (!defined(dataSource._name)) {
dataSource._name = name;
}
var styleCollection = new EntityCollection(dataSource);
return when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver, context)).then(function() {
var element = kml.documentElement;
if (element.localName === 'kml') {
var childNodes = element.childNodes;
for (var i = 0; i < childNodes.length; i++) {
var tmp = childNodes[i];
if (defined(featureTypes[tmp.localName])) {
element = tmp;
break;
}
}
}
entityCollection.suspendEvents();
processFeatureNode(dataSource, element, undefined, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
entityCollection.resumeEvents();
return when.all(promises).then(function() {
return kml.documentElement;
});
});
}
function loadKmz(dataSource, entityCollection, blob, sourceUri) {
var deferred = when.defer();
zip.createReader(new zip.BlobReader(blob), function(reader) {
reader.getEntries(function(entries) {
var promises = [];
var uriResolver = {};
var docEntry;
var docDefer;
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
if (!entry.directory) {
var innerDefer = when.defer();
promises.push(innerDefer.promise);
if (/\.kml$/i.test(entry.filename)) {
// We use the first KML document we come across
// https://developers.google.com/kml/documentation/kmzarchives
// Unless we come across a .kml file at the root of the archive because GE does this
if (!defined(docEntry) || !/\//i.test(entry.filename)) {
if (defined(docEntry)) {
// We found one at the root so load the initial kml as a data uri
loadDataUriFromZip(reader, docEntry, uriResolver, docDefer);
}
docEntry = entry;
docDefer = innerDefer;
} else {
// Wasn't the first kml and wasn't at the root
loadDataUriFromZip(reader, entry, uriResolver, innerDefer);
}
} else {
loadDataUriFromZip(reader, entry, uriResolver, innerDefer);
}
}
}
// Now load the root KML document
if (defined(docEntry)) {
loadXmlFromZip(reader, docEntry, uriResolver, docDefer);
}
when.all(promises).then(function() {
reader.close();
if (!defined(uriResolver.kml)) {
deferred.reject(new RuntimeError('KMZ file does not contain a KML document.'));
return;
}
uriResolver.keys = Object.keys(uriResolver);
return loadKml(dataSource, entityCollection, uriResolver.kml, sourceUri, uriResolver);
}).then(deferred.resolve).otherwise(deferred.reject);
});
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
}
function load(dataSource, entityCollection, data, options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var sourceUri = options.sourceUri;
var uriResolver = options.uriResolver;
var context = options.context;
var promise = data;
if (typeof data === 'string') {
promise = loadBlob(proxyUrl(data, dataSource._proxy));
sourceUri = defaultValue(sourceUri, data);
}
return when(promise)
.then(function(dataToLoad) {
if (dataToLoad instanceof Blob) {
return isZipFile(dataToLoad).then(function(isZip) {
if (isZip) {
return loadKmz(dataSource, entityCollection, dataToLoad, sourceUri);
}
return readBlobAsText(dataToLoad).then(function(text) {
//There's no official way to validate if a parse was successful.
//The following check detects the error on various browsers.
//IE raises an exception
var kml;
var error;
try {
kml = parser.parseFromString(text, 'application/xml');
} catch (e) {
error = e.toString();
}
//The parse succeeds on Chrome and Firefox, but the error
//handling is different in each.
if (defined(error) || kml.body || kml.documentElement.tagName === 'parsererror') {
//Firefox has error information as the firstChild nodeValue.
var msg = defined(error) ? error : kml.documentElement.firstChild.nodeValue;
//Chrome has it in the body text.
if (!msg) {
msg = kml.body.innerText;
}
//Return the error
throw new RuntimeError(msg);
}
return loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context);
});
});
} else {
return loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver, context);
}
})
.otherwise(function(error) {
dataSource._error.raiseEvent(dataSource, error);
console.log(error);
return when.reject(error);
});
}
/**
* A {@link DataSource} which processes Keyhole Markup Language 2.2 (KML).
*
* KML support in Cesium is incomplete, but a large amount of the standard,
* as well as Google's gx
extension namespace, is supported. See Github issue
* {@link https://github.com/AnalyticalGraphicsInc/cesium/issues/873|#873} for a
* detailed list of what is and isn't support. Cesium will also write information to the
* console when it encounters most unsupported features.
*
*
* Non visual feature data, such as atom:author
and ExtendedData
* is exposed via an instance of {@link KmlFeatureData}, which is added to each {@link Entity}
* under the kml
property.
*
*
* @alias KmlDataSource
* @constructor
*
* @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
* @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
* @param {DefaultProxy} [options.proxy] A proxy to be used for loading external data.
*
* @see {@link http://www.opengeospatial.org/standards/kml/|Open Geospatial Consortium KML Standard}
* @see {@link https://developers.google.com/kml/|Google KML Documentation}
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=KML.html|Cesium Sandcastle KML Demo}
*
* @example
* var viewer = new Cesium.Viewer('cesiumContainer');
* viewer.dataSources.add(Cesium.KmlDataSource.load('../../SampleData/facilities.kmz'),
* {
* camera: viewer.scene.camera,
* canvas: viewer.scene.canvas
* });
*/
function KmlDataSource(options) {
options = defaultValue(options, {});
var camera = options.camera;
var canvas = options.canvas;
if (!defined(camera)) {
throw new DeveloperError('options.camera is required.');
}
if (!defined(canvas)) {
throw new DeveloperError('options.canvas is required.');
}
this._changed = new Event();
this._error = new Event();
this._loading = new Event();
this._refresh = new Event();
this._unsupportedNode = new Event();
this._clock = undefined;
this._entityCollection = new EntityCollection(this);
this._name = undefined;
this._isLoading = false;
this._proxy = options.proxy;
this._pinBuilder = new PinBuilder();
this._networkLinks = new AssociativeArray();
this._entityCluster = new EntityCluster();
this._canvas = canvas;
this._camera = camera;
this._lastCameraView = {
position : defined(camera) ? Cartesian3.clone(camera.positionWC) : undefined,
direction : defined(camera) ? Cartesian3.clone(camera.directionWC) : undefined,
up : defined(camera) ? Cartesian3.clone(camera.upWC) : undefined,
bbox : defined(camera) ? camera.computeViewRectangle() : Rectangle.clone(Rectangle.MAX_VALUE)
};
}
/**
* Creates a Promise to a new instance loaded with the provided KML data.
*
* @param {String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
* @param {Object} [options] An object with the following properties:
* @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
* @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
* @param {DefaultProxy} [options.proxy] A proxy to be used for loading external data.
* @param {String} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
* @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
*
* @returns {Promise.} A promise that will resolve to a new KmlDataSource instance once the KML is loaded.
*/
KmlDataSource.load = function(data, options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var dataSource = new KmlDataSource(options);
return dataSource.load(data, options);
};
defineProperties(KmlDataSource.prototype, {
/**
* Gets a human-readable name for this instance.
* This will be automatically be set to the KML document name on load.
* @memberof KmlDataSource.prototype
* @type {String}
*/
name : {
get : function() {
return this._name;
}
},
/**
* Gets the clock settings defined by the loaded KML. This represents the total
* availability interval for all time-dynamic data. If the KML does not contain
* time-dynamic data, this value is undefined.
* @memberof KmlDataSource.prototype
* @type {DataSourceClock}
*/
clock : {
get : function() {
return this._clock;
}
},
/**
* Gets the collection of {@link Entity} instances.
* @memberof KmlDataSource.prototype
* @type {EntityCollection}
*/
entities : {
get : function() {
return this._entityCollection;
}
},
/**
* Gets a value indicating if the data source is currently loading data.
* @memberof KmlDataSource.prototype
* @type {Boolean}
*/
isLoading : {
get : function() {
return this._isLoading;
}
},
/**
* Gets an event that will be raised when the underlying data changes.
* @memberof KmlDataSource.prototype
* @type {Event}
*/
changedEvent : {
get : function() {
return this._changed;
}
},
/**
* Gets an event that will be raised if an error is encountered during processing.
* @memberof KmlDataSource.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._error;
}
},
/**
* Gets an event that will be raised when the data source either starts or stops loading.
* @memberof KmlDataSource.prototype
* @type {Event}
*/
loadingEvent : {
get : function() {
return this._loading;
}
},
/**
* Gets an event that will be raised when the data source refreshes a network link.
* @memberof KmlDataSource.prototype
* @type {Event}
*/
refreshEvent : {
get : function() {
return this._refresh;
}
},
/**
* Gets an event that will be raised when the data source finds an unsupported node type.
* @memberof KmlDataSource.prototype
* @type {Event}
*/
unsupportedNodeEvent : {
get : function() {
return this._unsupportedNode;
}
},
/**
* Gets whether or not this data source should be displayed.
* @memberof KmlDataSource.prototype
* @type {Boolean}
*/
show : {
get : function() {
return this._entityCollection.show;
},
set : function(value) {
this._entityCollection.show = value;
}
},
/**
* Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
*
* @memberof KmlDataSource.prototype
* @type {EntityCluster}
*/
clustering : {
get : function() {
return this._entityCluster;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value must be defined.');
}
this._entityCluster = value;
}
}
});
/**
* Asynchronously loads the provided KML data, replacing any existing data.
*
* @param {String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
* @param {Object} [options] An object with the following properties:
* @param {Number} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
* @returns {Promise.} A promise that will resolve to this instances once the KML is loaded.
* @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
*/
KmlDataSource.prototype.load = function(data, options) {
if (!defined(data)) {
throw new DeveloperError('data is required.');
}
options = defaultValue(options, {});
DataSource.setLoading(this, true);
var oldName = this._name;
this._name = undefined;
this._clampToGround = defaultValue(options.clampToGround, false);
var that = this;
return load(this, this._entityCollection, data, options).then(function() {
var clock;
var availability = that._entityCollection.computeAvailability();
var start = availability.start;
var stop = availability.stop;
var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
if (!isMinStart || !isMaxStop) {
var date;
//If start is min time just start at midnight this morning, local time
if (isMinStart) {
date = new Date();
date.setHours(0, 0, 0, 0);
start = JulianDate.fromDate(date);
}
//If stop is max value just stop at midnight tonight, local time
if (isMaxStop) {
date = new Date();
date.setHours(24, 0, 0, 0);
stop = JulianDate.fromDate(date);
}
clock = new DataSourceClock();
clock.startTime = start;
clock.stopTime = stop;
clock.currentTime = JulianDate.clone(start);
clock.clockRange = ClockRange.LOOP_STOP;
clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
clock.multiplier = Math.round(Math.min(Math.max(JulianDate.secondsDifference(stop, start) / 60, 1), 3.15569e7));
}
var changed = false;
if (clock !== that._clock) {
that._clock = clock;
changed = true;
}
if (oldName !== that._name) {
changed = true;
}
if (changed) {
that._changed.raiseEvent(that);
}
DataSource.setLoading(that, false);
return that;
}).otherwise(function(error) {
DataSource.setLoading(that, false);
that._error.raiseEvent(that, error);
console.log(error);
return when.reject(error);
});
};
function mergeAvailabilityWithParent(child) {
var parent = child.parent;
if (defined(parent)) {
var parentAvailability = parent.availability;
if (defined(parentAvailability)) {
var childAvailability = child.availability;
if (defined(childAvailability)) {
childAvailability.intersect(parentAvailability);
} else {
child.availability = parentAvailability;
}
}
}
}
function getNetworkLinkUpdateCallback(dataSource, networkLink, newEntityCollection, networkLinks, processedHref) {
return function(rootElement) {
if (!networkLinks.contains(networkLink.id)) {
// Got into the odd case where a parent network link was updated while a child
// network link update was in flight, so just throw it away.
return;
}
var remove = false;
var networkLinkControl = queryFirstNode(rootElement, 'NetworkLinkControl', namespaces.kml);
var hasNetworkLinkControl = defined(networkLinkControl);
var minRefreshPeriod = 0;
if (hasNetworkLinkControl) {
if (defined(queryFirstNode(networkLinkControl, 'Update', namespaces.kml))) {
console.log('KML - NetworkLinkControl updates aren\'t supported.');
networkLink.updating = false;
networkLinks.remove(networkLink.id);
return;
}
networkLink.cookie = defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), '');
minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0);
}
var now = JulianDate.now();
var refreshMode = networkLink.refreshMode;
if (refreshMode === RefreshMode.INTERVAL) {
if (defined(networkLinkControl)) {
networkLink.time = Math.max(minRefreshPeriod, networkLink.time);
}
} else if (refreshMode === RefreshMode.EXPIRE) {
var expires;
if (defined(networkLinkControl)) {
expires = queryStringValue(networkLinkControl, 'expires', namespaces.kml);
}
if (defined(expires)) {
try {
var date = JulianDate.fromIso8601(expires);
var diff = JulianDate.secondsDifference(date, now);
if (diff > 0 && diff < minRefreshPeriod) {
JulianDate.addSeconds(now, minRefreshPeriod, date);
}
networkLink.time = date;
} catch (e) {
console.log('KML - NetworkLinkControl expires is not a valid date');
remove = true;
}
} else {
console.log('KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element');
remove = true;
}
}
var networkLinkEntity = networkLink.entity;
var entityCollection = dataSource._entityCollection;
var newEntities = newEntityCollection.values;
function removeChildren(entity) {
entityCollection.remove(entity);
var children = entity._children;
var count = children.length;
for(var i=0;i networkLink.time) {
doUpdate = true;
}
}
else if (networkLink.refreshMode === RefreshMode.EXPIRE) {
if (JulianDate.greaterThan(now, networkLink.time)) {
doUpdate = true;
}
} else if (networkLink.refreshMode === RefreshMode.STOP) {
if (cameraViewUpdate) {
networkLink.needsUpdate = true;
networkLink.cameraUpdateTime = now;
}
if (networkLink.needsUpdate && JulianDate.secondsDifference(now, networkLink.cameraUpdateTime) >= networkLink.time) {
doUpdate = true;
}
}
if (doUpdate) {
recurseIgnoreEntities(entity);
networkLink.updating = true;
var newEntityCollection = new EntityCollection();
var href = joinUrls(networkLink.href, makeQueryString(networkLink.cookie, networkLink.queryString), false);
href = processNetworkLinkQueryString(that._camera, that._canvas, href, networkLink.viewBoundScale, lastCameraView.bbox);
load(that, newEntityCollection, href, {context: entity.id})
.then(getNetworkLinkUpdateCallback(that, networkLink, newEntityCollection, newNetworkLinks, href))
.otherwise(function(error) {
var msg = 'NetworkLink ' + networkLink.href + ' refresh failed: ' + error;
console.log(msg);
that._error.raiseEvent(that, msg);
});
changed = true;
}
}
newNetworkLinks.set(networkLink.id, networkLink);
});
if (changed) {
this._networkLinks = newNetworkLinks;
this._changed.raiseEvent(this);
}
return true;
};
/**
* Contains KML Feature data loaded into the Entity.kml
property by {@link KmlDataSource}.
* @alias KmlFeatureData
* @constructor
*/
function KmlFeatureData() {
/**
* Gets the atom syndication format author field.
* @type Object
*/
this.author = {
/**
* Gets the name.
* @type String
* @alias author.name
* @memberof! KmlFeatureData#
* @property author.name
*/
name : undefined,
/**
* Gets the URI.
* @type String
* @alias author.uri
* @memberof! KmlFeatureData#
* @property author.uri
*/
uri : undefined,
/**
* Gets the email.
* @type String
* @alias author.email
* @memberof! KmlFeatureData#
* @property author.email
*/
email : undefined
};
/**
* Gets the link.
* @type Object
*/
this.link = {
/**
* Gets the href.
* @type String
* @alias link.href
* @memberof! KmlFeatureData#
* @property link.href
*/
href : undefined,
/**
* Gets the language of the linked resource.
* @type String
* @alias link.hreflang
* @memberof! KmlFeatureData#
* @property link.hreflang
*/
hreflang : undefined,
/**
* Gets the link relation.
* @type String
* @alias link.rel
* @memberof! KmlFeatureData#
* @property link.rel
*/
rel : undefined,
/**
* Gets the link type.
* @type String
* @alias link.type
* @memberof! KmlFeatureData#
* @property link.type
*/
type : undefined,
/**
* Gets the link title.
* @type String
* @alias link.title
* @memberof! KmlFeatureData#
* @property link.title
*/
title : undefined,
/**
* Gets the link length.
* @type String
* @alias link.length
* @memberof! KmlFeatureData#
* @property link.length
*/
length : undefined
};
/**
* Gets the unstructured address field.
* @type String
*/
this.address = undefined;
/**
* Gets the phone number.
* @type String
*/
this.phoneNumber = undefined;
/**
* Gets the snippet.
* @type String
*/
this.snippet = undefined;
/**
* Gets the extended data, parsed into a JSON object.
* Currently only the Data
property is supported.
* SchemaData
and custom data are ignored.
* @type String
*/
this.extendedData = undefined;
}
return KmlDataSource;
});
/*global define*/
define('DataSources/VelocityOrientationProperty',[
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Ellipsoid',
'../Core/Event',
'../Core/Matrix3',
'../Core/Quaternion',
'../Core/Transforms',
'./Property',
'./VelocityVectorProperty'
], function(
Cartesian3,
defaultValue,
defined,
defineProperties,
Ellipsoid,
Event,
Matrix3,
Quaternion,
Transforms,
Property,
VelocityVectorProperty) {
'use strict';
/**
* A {@link Property} which evaluates to a {@link Quaternion} rotation
* based on the velocity of the provided {@link PositionProperty}.
*
* @alias VelocityOrientationProperty
* @constructor
*
* @param {Property} [position] The position property used to compute the orientation.
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine which way is up.
*
* @example
* //Create an entity with position and orientation.
* var position = new Cesium.SampledProperty();
* position.addSamples(...);
* var entity = viewer.entities.add({
* position : position,
* orientation : new Cesium.VelocityOrientationProperty(position)
* }));
*/
function VelocityOrientationProperty(position, ellipsoid) {
this._velocityVectorProperty = new VelocityVectorProperty(position, true);
this._subscription = undefined;
this._ellipsoid = undefined;
this._definitionChanged = new Event();
this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var that = this;
this._velocityVectorProperty.definitionChanged.addEventListener(function() {
that._definitionChanged.raiseEvent(that);
});
}
defineProperties(VelocityOrientationProperty.prototype, {
/**
* Gets a value indicating if this property is constant.
* @memberof VelocityOrientationProperty.prototype
*
* @type {Boolean}
* @readonly
*/
isConstant : {
get : function() {
return Property.isConstant(this._velocityVectorProperty);
}
},
/**
* Gets the event that is raised whenever the definition of this property changes.
* @memberof VelocityOrientationProperty.prototype
*
* @type {Event}
* @readonly
*/
definitionChanged : {
get : function() {
return this._definitionChanged;
}
},
/**
* Gets or sets the position property used to compute orientation.
* @memberof VelocityOrientationProperty.prototype
*
* @type {Property}
*/
position : {
get : function() {
return this._velocityVectorProperty.position;
},
set : function(value) {
this._velocityVectorProperty.position = value;
}
},
/**
* Gets or sets the ellipsoid used to determine which way is up.
* @memberof VelocityOrientationProperty.prototype
*
* @type {Property}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
},
set : function(value) {
var oldValue = this._ellipsoid;
if (oldValue !== value) {
this._ellipsoid = value;
this._definitionChanged.raiseEvent(this);
}
}
}
});
var positionScratch = new Cartesian3();
var velocityScratch = new Cartesian3();
var rotationScratch = new Matrix3();
/**
* Gets the value of the property at the provided time.
*
* @param {JulianDate} [time] The time for which to retrieve the value.
* @param {Quaternion} [result] The object to store the value into, if omitted, a new instance is created and returned.
* @returns {Quaternion} The modified result parameter or a new instance if the result parameter was not supplied.
*/
VelocityOrientationProperty.prototype.getValue = function(time, result) {
var velocity = this._velocityVectorProperty._getValue(time, velocityScratch, positionScratch);
if (!defined(velocity)) {
return undefined;
}
Transforms.rotationMatrixFromPositionVelocity(positionScratch, velocity, this._ellipsoid, rotationScratch);
return Quaternion.fromRotationMatrix(rotationScratch, result);
};
/**
* Compares this property to the provided property and returns
* true
if they are equal, false
otherwise.
*
* @param {Property} [other] The other property.
* @returns {Boolean} true
if left and right are equal, false
otherwise.
*/
VelocityOrientationProperty.prototype.equals = function(other) {
return this === other ||//
(other instanceof VelocityOrientationProperty &&
Property.equals(this._velocityVectorProperty, other._velocityVectorProperty) &&
(this._ellipsoid === other._ellipsoid ||
this._ellipsoid.equals(other._ellipsoid)));
};
return VelocityOrientationProperty;
});
/*global define*/
define('DataSources/Visualizer',[
'../Core/DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* Defines the interface for visualizers. Visualizers are plug-ins to
* {@link DataSourceDisplay} that render data associated with
* {@link DataSource} instances.
* This object is an interface for documentation purposes and is not intended
* to be instantiated directly.
* @alias Visualizer
* @constructor
*
* @see BillboardVisualizer
* @see LabelVisualizer
* @see ModelVisualizer
* @see PathVisualizer
* @see PointVisualizer
* @see GeometryVisualizer
*/
function Visualizer() {
DeveloperError.throwInstantiationError();
}
/**
* Updates the visualization to the provided time.
* @function
*
* @param {JulianDate} time The time.
*
* @returns {Boolean} True if the display was updated to the provided time,
* false if the visualizer is waiting for an asynchronous operation to
* complete before data can be updated.
*/
Visualizer.prototype.update = DeveloperError.throwInstantiationError;
/**
* Computes a bounding sphere which encloses the visualization produced for the specified entity.
* The bounding sphere is in the fixed frame of the scene's globe.
*
* @param {Entity} entity The entity whose bounding sphere to compute.
* @param {BoundingSphere} result The bounding sphere onto which to store the result.
* @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
* BoundingSphereState.PENDING if the result is still being computed, or
* BoundingSphereState.FAILED if the entity has no visualization in the current scene.
* @private
*/
Visualizer.prototype.getBoundingSphere = DeveloperError.throwInstantiationError;
/**
* Returns true if this object was destroyed; otherwise, false.
* @function
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*/
Visualizer.prototype.isDestroyed = DeveloperError.throwInstantiationError;
/**
* Removes all visualization and cleans up any resources associated with this instance.
* @function
*/
Visualizer.prototype.destroy = DeveloperError.throwInstantiationError;
return Visualizer;
});
/*global define*/
define('Renderer/ClearCommand',[
'../Core/Color',
'../Core/defaultValue',
'../Core/freezeObject'
], function(
Color,
defaultValue,
freezeObject) {
'use strict';
/**
* Represents a command to the renderer for clearing a framebuffer.
*
* @private
*/
function ClearCommand(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The value to clear the color buffer to. When undefined
, the color buffer is not cleared.
*
* @type {Color}
*
* @default undefined
*/
this.color = options.color;
/**
* The value to clear the depth buffer to. When undefined
, the depth buffer is not cleared.
*
* @type {Number}
*
* @default undefined
*/
this.depth = options.depth;
/**
* The value to clear the stencil buffer to. When undefined
, the stencil buffer is not cleared.
*
* @type {Number}
*
* @default undefined
*/
this.stencil = options.stencil;
/**
* The render state to apply when executing the clear command. The following states affect clearing:
* scissor test, color mask, depth mask, and stencil mask. When the render state is
* undefined
, the default render state is used.
*
* @type {RenderState}
*
* @default undefined
*/
this.renderState = options.renderState;
/**
* The framebuffer to clear.
*
* @type {Framebuffer}
*
* @default undefined
*/
this.framebuffer = options.framebuffer;
/**
* The object who created this command. This is useful for debugging command
* execution; it allows you to see who created a command when you only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {Object}
*
* @default undefined
*
* @see Scene#debugCommandFilter
*/
this.owner = options.owner;
}
/**
* Clears color to (0.0, 0.0, 0.0, 0.0); depth to 1.0; and stencil to 0.
*
* @type {ClearCommand}
*
* @constant
*/
ClearCommand.ALL = freezeObject(new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0),
depth : 1.0,
stencil : 0.0
}));
ClearCommand.prototype.execute = function(context, passState) {
context.clear(this, passState);
};
return ClearCommand;
});
/*global define*/
define('Renderer/ComputeCommand',[
'../Core/defaultValue',
'./Pass'
], function(
defaultValue,
Pass) {
'use strict';
/**
* Represents a command to the renderer for GPU Compute (using old-school GPGPU).
*
* @private
*/
function ComputeCommand(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The vertex array. If none is provided, a viewport quad will be used.
*
* @type {VertexArray}
* @default undefined
*/
this.vertexArray = options.vertexArray;
/**
* The fragment shader source. The default vertex shader is ViewportQuadVS.
*
* @type {ShaderSource}
* @default undefined
*/
this.fragmentShaderSource = options.fragmentShaderSource;
/**
* The shader program to apply.
*
* @type {ShaderProgram}
* @default undefined
*/
this.shaderProgram = options.shaderProgram;
/**
* An object with functions whose names match the uniforms in the shader program
* and return values to set those uniforms.
*
* @type {Object}
* @default undefined
*/
this.uniformMap = options.uniformMap;
/**
* Texture to use for offscreen rendering.
*
* @type {Texture}
* @default undefined
*/
this.outputTexture = options.outputTexture;
/**
* Function that is called immediately before the ComputeCommand is executed. Used to
* update any renderer resources. Takes the ComputeCommand as its single argument.
*
* @type {Function}
* @default undefined
*/
this.preExecute = options.preExecute;
/**
* Function that is called after the ComputeCommand is executed. Takes the output
* texture as its single argument.
*
* @type {Function}
* @default undefined
*/
this.postExecute = options.postExecute;
/**
* Whether the renderer resources will persist beyond this call. If not, they
* will be destroyed after completion.
*
* @type {Boolean}
* @default false
*/
this.persists = defaultValue(options.persists, false);
/**
* The pass when to render. Always compute pass.
*
* @type {Pass}
* @default Pass.COMPUTE;
*/
this.pass = Pass.COMPUTE;
/**
* The object who created this command. This is useful for debugging command
* execution; it allows us to see who created a command when we only have a
* reference to the command, and can be used to selectively execute commands
* with {@link Scene#debugCommandFilter}.
*
* @type {Object}
* @default undefined
*
* @see Scene#debugCommandFilter
*/
this.owner = options.owner;
}
/**
* Executes the compute command.
*
* @param {Context} context The context that processes the compute command.
*/
ComputeCommand.prototype.execute = function(computeEngine) {
computeEngine.execute(this);
};
return ComputeCommand;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ViewportQuadVS',[],function() {
'use strict';
return "attribute vec4 position;\n\
attribute vec2 textureCoordinates;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
gl_Position = position;\n\
v_textureCoordinates = textureCoordinates;\n\
}\n\
";
});
/*global define*/
define('Renderer/ComputeEngine',[
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/PrimitiveType',
'../Shaders/ViewportQuadVS',
'./ClearCommand',
'./DrawCommand',
'./Framebuffer',
'./RenderState',
'./ShaderProgram'
], function(
BoundingRectangle,
Color,
defined,
destroyObject,
DeveloperError,
PrimitiveType,
ViewportQuadVS,
ClearCommand,
DrawCommand,
Framebuffer,
RenderState,
ShaderProgram) {
'use strict';
/**
* @private
*/
function ComputeEngine(context) {
this._context = context;
}
var renderStateScratch;
var drawCommandScratch = new DrawCommand({
primitiveType : PrimitiveType.TRIANGLES
});
var clearCommandScratch = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0)
});
function createFramebuffer(context, outputTexture) {
return new Framebuffer({
context : context,
colorTextures : [outputTexture],
destroyAttachments : false
});
}
function createViewportQuadShader(context, fragmentShaderSource) {
return ShaderProgram.fromCache({
context : context,
vertexShaderSource : ViewportQuadVS,
fragmentShaderSource : fragmentShaderSource,
attributeLocations : {
position : 0,
textureCoordinates : 1
}
});
}
function createRenderState(width, height) {
if ((!defined(renderStateScratch)) ||
(renderStateScratch.viewport.width !== width) ||
(renderStateScratch.viewport.height !== height)) {
renderStateScratch = RenderState.fromCache({
viewport : new BoundingRectangle(0, 0, width, height)
});
}
return renderStateScratch;
}
ComputeEngine.prototype.execute = function(computeCommand) {
if (!defined(computeCommand)) {
throw new DeveloperError('computeCommand is required.');
}
// This may modify the command's resources, so do error checking afterwards
if (defined(computeCommand.preExecute)) {
computeCommand.preExecute(computeCommand);
}
if (!defined(computeCommand.fragmentShaderSource) && !defined(computeCommand.shaderProgram)) {
throw new DeveloperError('computeCommand.fragmentShaderSource or computeCommand.shaderProgram is required.');
}
if (!defined(computeCommand.outputTexture)) {
throw new DeveloperError('computeCommand.outputTexture is required.');
}
var outputTexture = computeCommand.outputTexture;
var width = outputTexture.width;
var height = outputTexture.height;
var context = this._context;
var vertexArray = defined(computeCommand.vertexArray) ? computeCommand.vertexArray : context.getViewportQuadVertexArray();
var shaderProgram = defined(computeCommand.shaderProgram) ? computeCommand.shaderProgram : createViewportQuadShader(context, computeCommand.fragmentShaderSource);
var framebuffer = createFramebuffer(context, outputTexture);
var renderState = createRenderState(width, height);
var uniformMap = computeCommand.uniformMap;
var clearCommand = clearCommandScratch;
clearCommand.framebuffer = framebuffer;
clearCommand.renderState = renderState;
clearCommand.execute(context);
var drawCommand = drawCommandScratch;
drawCommand.vertexArray = vertexArray;
drawCommand.renderState = renderState;
drawCommand.shaderProgram = shaderProgram;
drawCommand.uniformMap = uniformMap;
drawCommand.framebuffer = framebuffer;
drawCommand.execute(context);
framebuffer.destroy();
if (!computeCommand.persists) {
shaderProgram.destroy();
if (defined(computeCommand.vertexArray)) {
vertexArray.destroy();
}
}
if (defined(computeCommand.postExecute)) {
computeCommand.postExecute(outputTexture);
}
};
ComputeEngine.prototype.isDestroyed = function() {
return false;
};
ComputeEngine.prototype.destroy = function() {
return destroyObject(this);
};
return ComputeEngine;
});
/*global define*/
define('Renderer/PassState',[], function() {
'use strict';
/**
* The state for a particular rendering pass. This is used to supplement the state
* in a command being executed.
*
* @private
*/
function PassState(context) {
/**
* The context used to execute commands for this pass.
*
* @type {Context}
*/
this.context = context;
/**
* The framebuffer to render to. This framebuffer is used unless a {@link DrawCommand}
* or {@link ClearCommand} explicitly define a framebuffer, which is used for off-screen
* rendering.
*
* @type {Framebuffer}
* @default undefined
*/
this.framebuffer = undefined;
/**
* When defined, this overrides the blending property of a {@link DrawCommand}'s render state.
* This is used to, for example, to allow the renderer to turn off blending during the picking pass.
*
* When this is undefined
, the {@link DrawCommand}'s property is used.
*
*
* @type {Boolean}
* @default undefined
*/
this.blendingEnabled = undefined;
/**
* When defined, this overrides the scissor test property of a {@link DrawCommand}'s render state.
* This is used to, for example, to allow the renderer to scissor out the pick region during the picking pass.
*
* When this is undefined
, the {@link DrawCommand}'s property is used.
*
*
* @type {Object}
* @default undefined
*/
this.scissorTest = undefined;
/**
* The viewport used when one is not defined by a {@link DrawCommand}'s render state.
* @type {BoundingRectangle}
* @default undefined
*/
this.viewport = undefined;
}
return PassState;
});
/*global define*/
define('Renderer/RenderbufferFormat',[
'../Core/freezeObject',
'../Core/WebGLConstants'
], function(
freezeObject,
WebGLConstants) {
'use strict';
/**
* @private
*/
var RenderbufferFormat = {
RGBA4 : WebGLConstants.RGBA4,
RGB5_A1 : WebGLConstants.RGB5_A1,
RGB565 : WebGLConstants.RGB565,
DEPTH_COMPONENT16 : WebGLConstants.DEPTH_COMPONENT16,
STENCIL_INDEX8 : WebGLConstants.STENCIL_INDEX8,
DEPTH_STENCIL : WebGLConstants.DEPTH_STENCIL,
validate : function(renderbufferFormat) {
return ((renderbufferFormat === RenderbufferFormat.RGBA4) ||
(renderbufferFormat === RenderbufferFormat.RGB5_A1) ||
(renderbufferFormat === RenderbufferFormat.RGB565) ||
(renderbufferFormat === RenderbufferFormat.DEPTH_COMPONENT16) ||
(renderbufferFormat === RenderbufferFormat.STENCIL_INDEX8) ||
(renderbufferFormat === RenderbufferFormat.DEPTH_STENCIL));
}
};
return freezeObject(RenderbufferFormat);
});
/*global define*/
define('Renderer/Renderbuffer',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'./ContextLimits',
'./RenderbufferFormat'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
ContextLimits,
RenderbufferFormat) {
'use strict';
/**
* @private
*/
function Renderbuffer(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
var context = options.context;
var gl = context._gl;
var maximumRenderbufferSize = ContextLimits.maximumRenderbufferSize;
var format = defaultValue(options.format, RenderbufferFormat.RGBA4);
var width = defined(options.width) ? options.width : gl.drawingBufferWidth;
var height = defined(options.height) ? options.height : gl.drawingBufferHeight;
if (!RenderbufferFormat.validate(format)) {
throw new DeveloperError('Invalid format.');
}
if (width <= 0) {
throw new DeveloperError('Width must be greater than zero.');
}
if (width > maximumRenderbufferSize) {
throw new DeveloperError('Width must be less than or equal to the maximum renderbuffer size (' + maximumRenderbufferSize + '). Check maximumRenderbufferSize.');
}
if (height <= 0) {
throw new DeveloperError('Height must be greater than zero.');
}
if (height > maximumRenderbufferSize) {
throw new DeveloperError('Height must be less than or equal to the maximum renderbuffer size (' + maximumRenderbufferSize + '). Check maximumRenderbufferSize.');
}
this._gl = gl;
this._format = format;
this._width = width;
this._height = height;
this._renderbuffer = this._gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this._renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, format, width, height);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
defineProperties(Renderbuffer.prototype, {
format: {
get : function() {
return this._format;
}
},
width: {
get : function() {
return this._width;
}
},
height: {
get : function() {
return this._height;
}
}
});
Renderbuffer.prototype._getRenderbuffer = function() {
return this._renderbuffer;
};
Renderbuffer.prototype.isDestroyed = function() {
return false;
};
Renderbuffer.prototype.destroy = function() {
this._gl.deleteRenderbuffer(this._renderbuffer);
return destroyObject(this);
};
return Renderbuffer;
});
/*global define*/
define('Renderer/PickFramebuffer',[
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'./Framebuffer',
'./PassState',
'./Renderbuffer',
'./RenderbufferFormat',
'./Texture'
], function(
BoundingRectangle,
Color,
defaultValue,
defined,
destroyObject,
Framebuffer,
PassState,
Renderbuffer,
RenderbufferFormat,
Texture) {
'use strict';
/**
* @private
*/
function PickFramebuffer(context) {
// Override per-command states
var passState = new PassState(context);
passState.blendingEnabled = false;
passState.scissorTest = {
enabled : true,
rectangle : new BoundingRectangle()
};
passState.viewport = new BoundingRectangle();
this._context = context;
this._fb = undefined;
this._passState = passState;
this._width = 0;
this._height = 0;
}
PickFramebuffer.prototype.begin = function(screenSpaceRectangle) {
var context = this._context;
var width = context.drawingBufferWidth;
var height = context.drawingBufferHeight;
BoundingRectangle.clone(screenSpaceRectangle, this._passState.scissorTest.rectangle);
// Initially create or recreate renderbuffers and framebuffer used for picking
if ((!defined(this._fb)) || (this._width !== width) || (this._height !== height)) {
this._width = width;
this._height = height;
this._fb = this._fb && this._fb.destroy();
this._fb = new Framebuffer({
context : context,
colorTextures : [new Texture({
context : context,
width : width,
height : height
})],
depthStencilRenderbuffer : new Renderbuffer({
context : context,
format : RenderbufferFormat.DEPTH_STENCIL
})
});
this._passState.framebuffer = this._fb;
}
this._passState.viewport.width = width;
this._passState.viewport.height = height;
return this._passState;
};
var colorScratch = new Color();
PickFramebuffer.prototype.end = function(screenSpaceRectangle) {
var width = defaultValue(screenSpaceRectangle.width, 1.0);
var height = defaultValue(screenSpaceRectangle.height, 1.0);
var context = this._context;
var pixels = context.readPixels({
x : screenSpaceRectangle.x,
y : screenSpaceRectangle.y,
width : width,
height : height,
framebuffer : this._fb
});
var max = Math.max(width, height);
var length = max * max;
var halfWidth = Math.floor(width * 0.5);
var halfHeight = Math.floor(height * 0.5);
var x = 0;
var y = 0;
var dx = 0;
var dy = -1;
// Spiral around the center pixel, this is a workaround until
// we can access the depth buffer on all browsers.
// The region does not have to square and the dimensions do not have to be odd, but
// loop iterations would be wasted. Prefer square regions where the size is odd.
for (var i = 0; i < length; ++i) {
if (-halfWidth <= x && x <= halfWidth && -halfHeight <= y && y <= halfHeight) {
var index = 4 * ((halfHeight - y) * width + x + halfWidth);
colorScratch.red = Color.byteToFloat(pixels[index]);
colorScratch.green = Color.byteToFloat(pixels[index + 1]);
colorScratch.blue = Color.byteToFloat(pixels[index + 2]);
colorScratch.alpha = Color.byteToFloat(pixels[index + 3]);
var object = context.getObjectByPickColor(colorScratch);
if (defined(object)) {
return object;
}
}
// if (top right || bottom left corners) || (top left corner) || (bottom right corner + (1, 0))
// change spiral direction
if (x === y || (x < 0 && -x === y) || (x > 0 && x === 1 - y)) {
var temp = dx;
dx = -dy;
dy = temp;
}
x += dx;
y += dy;
}
return undefined;
};
PickFramebuffer.prototype.isDestroyed = function() {
return false;
};
PickFramebuffer.prototype.destroy = function() {
this._fb = this._fb && this._fb.destroy();
return destroyObject(this);
};
return PickFramebuffer;
});
/*global define*/
define('Renderer/ShaderCache',[
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'./ShaderProgram',
'./ShaderSource'
], function(
defined,
defineProperties,
destroyObject,
ShaderProgram,
ShaderSource) {
'use strict';
/**
* @private
*/
function ShaderCache(context) {
this._context = context;
this._shaders = {};
this._numberOfShaders = 0;
this._shadersToRelease = {};
}
defineProperties(ShaderCache.prototype, {
numberOfShaders : {
get : function() {
return this._numberOfShaders;
}
}
});
/**
* Returns a shader program from the cache, or creates and caches a new shader program,
* given the GLSL vertex and fragment shader source and attribute locations.
*
* The difference between this and {@link ShaderCache#getShaderProgram}, is this is used to
* replace an existing reference to a shader program, which is passed as the first argument.
*
*
* @param {Object} options Object with the following properties:
* @param {ShaderProgram} [options.shaderProgram] The shader program that is being reassigned.
* @param {String|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
* @param {String|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
* @param {Object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
* @returns {ShaderProgram} The cached or newly created shader program.
*
*
* @example
* this._shaderProgram = context.shaderCache.replaceShaderProgram({
* shaderProgram : this._shaderProgram,
* vertexShaderSource : vs,
* fragmentShaderSource : fs,
* attributeLocations : attributeLocations
* });
*
* @see ShaderCache#getShaderProgram
*/
ShaderCache.prototype.replaceShaderProgram = function(options) {
if (defined(options.shaderProgram)) {
options.shaderProgram.destroy();
}
return this.getShaderProgram(options);
};
/**
* Returns a shader program from the cache, or creates and caches a new shader program,
* given the GLSL vertex and fragment shader source and attribute locations.
*
* @param {Object} options Object with the following properties:
* @param {String|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
* @param {String|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
* @param {Object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
*
* @returns {ShaderProgram} The cached or newly created shader program.
*/
ShaderCache.prototype.getShaderProgram = function(options) {
// convert shaders which are provided as strings into ShaderSource objects
// because ShaderSource handles all the automatic including of built-in functions, etc.
var vertexShaderSource = options.vertexShaderSource;
var fragmentShaderSource = options.fragmentShaderSource;
var attributeLocations = options.attributeLocations;
if (typeof vertexShaderSource === 'string') {
vertexShaderSource = new ShaderSource({
sources : [vertexShaderSource]
});
}
if (typeof fragmentShaderSource === 'string') {
fragmentShaderSource = new ShaderSource({
sources : [fragmentShaderSource]
});
}
var vertexShaderText = vertexShaderSource.createCombinedVertexShader();
var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader();
var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations);
var cachedShader;
if (this._shaders[keyword]) {
cachedShader = this._shaders[keyword];
// No longer want to release this if it was previously released.
delete this._shadersToRelease[keyword];
} else {
var context = this._context;
var shaderProgram = new ShaderProgram({
gl : context._gl,
logShaderCompilation : context.logShaderCompilation,
debugShaders : context.debugShaders,
vertexShaderSource : vertexShaderSource,
vertexShaderText : vertexShaderText,
fragmentShaderSource : fragmentShaderSource,
fragmentShaderText : fragmentShaderText,
attributeLocations : attributeLocations
});
cachedShader = {
cache : this,
shaderProgram : shaderProgram,
keyword : keyword,
count : 0
};
// A shader can't be in more than one cache.
shaderProgram._cachedShader = cachedShader;
this._shaders[keyword] = cachedShader;
++this._numberOfShaders;
}
++cachedShader.count;
return cachedShader.shaderProgram;
};
ShaderCache.prototype.destroyReleasedShaderPrograms = function() {
var shadersToRelease = this._shadersToRelease;
for ( var keyword in shadersToRelease) {
if (shadersToRelease.hasOwnProperty(keyword)) {
var cachedShader = shadersToRelease[keyword];
delete this._shaders[cachedShader.keyword];
cachedShader.shaderProgram.finalDestroy();
--this._numberOfShaders;
}
}
this._shadersToRelease = {};
};
ShaderCache.prototype.releaseShaderProgram = function(shaderProgram) {
if (defined(shaderProgram)) {
var cachedShader = shaderProgram._cachedShader;
if (cachedShader && (--cachedShader.count === 0)) {
this._shadersToRelease[cachedShader.keyword] = cachedShader;
}
}
};
ShaderCache.prototype.isDestroyed = function() {
return false;
};
ShaderCache.prototype.destroy = function() {
var shaders = this._shaders;
for ( var keyword in shaders) {
if (shaders.hasOwnProperty(keyword)) {
shaders[keyword].shaderProgram.finalDestroy();
}
}
return destroyObject(this);
};
return ShaderCache;
});
/*global define*/
define('Renderer/UniformState',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/defined',
'../Core/defineProperties',
'../Core/EncodedCartesian3',
'../Core/Math',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/Simon1994PlanetaryPositions',
'../Core/Transforms',
'../Scene/SceneMode'
], function(
BoundingRectangle,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
defined,
defineProperties,
EncodedCartesian3,
CesiumMath,
Matrix3,
Matrix4,
Simon1994PlanetaryPositions,
Transforms,
SceneMode) {
'use strict';
/**
* @private
*/
function UniformState() {
/**
* @type {Texture}
*/
this.globeDepthTexture = undefined;
this._viewport = new BoundingRectangle();
this._viewportCartesian4 = new Cartesian4();
this._viewportDirty = false;
this._viewportOrthographicMatrix = Matrix4.clone(Matrix4.IDENTITY);
this._viewportTransformation = Matrix4.clone(Matrix4.IDENTITY);
this._model = Matrix4.clone(Matrix4.IDENTITY);
this._view = Matrix4.clone(Matrix4.IDENTITY);
this._inverseView = Matrix4.clone(Matrix4.IDENTITY);
this._projection = Matrix4.clone(Matrix4.IDENTITY);
this._infiniteProjection = Matrix4.clone(Matrix4.IDENTITY);
this._entireFrustum = new Cartesian2();
this._currentFrustum = new Cartesian2();
this._frustumPlanes = new Cartesian4();
this._frameState = undefined;
this._temeToPseudoFixed = Matrix3.clone(Matrix4.IDENTITY);
// Derived members
this._view3DDirty = true;
this._view3D = new Matrix4();
this._inverseView3DDirty = true;
this._inverseView3D = new Matrix4();
this._inverseModelDirty = true;
this._inverseModel = new Matrix4();
this._inverseTransposeModelDirty = true;
this._inverseTransposeModel = new Matrix3();
this._viewRotation = new Matrix3();
this._inverseViewRotation = new Matrix3();
this._viewRotation3D = new Matrix3();
this._inverseViewRotation3D = new Matrix3();
this._inverseProjectionDirty = true;
this._inverseProjection = new Matrix4();
this._inverseProjectionOITDirty = true;
this._inverseProjectionOIT = new Matrix4();
this._modelViewDirty = true;
this._modelView = new Matrix4();
this._modelView3DDirty = true;
this._modelView3D = new Matrix4();
this._modelViewRelativeToEyeDirty = true;
this._modelViewRelativeToEye = new Matrix4();
this._inverseModelViewDirty = true;
this._inverseModelView = new Matrix4();
this._inverseModelView3DDirty = true;
this._inverseModelView3D = new Matrix4();
this._viewProjectionDirty = true;
this._viewProjection = new Matrix4();
this._inverseViewProjectionDirty = true;
this._inverseViewProjection = new Matrix4();
this._modelViewProjectionDirty = true;
this._modelViewProjection = new Matrix4();
this._inverseModelViewProjectionDirty = true;
this._inverseModelViewProjection = new Matrix4();
this._modelViewProjectionRelativeToEyeDirty = true;
this._modelViewProjectionRelativeToEye = new Matrix4();
this._modelViewInfiniteProjectionDirty = true;
this._modelViewInfiniteProjection = new Matrix4();
this._normalDirty = true;
this._normal = new Matrix3();
this._normal3DDirty = true;
this._normal3D = new Matrix3();
this._inverseNormalDirty = true;
this._inverseNormal = new Matrix3();
this._inverseNormal3DDirty = true;
this._inverseNormal3D = new Matrix3();
this._encodedCameraPositionMCDirty = true;
this._encodedCameraPositionMC = new EncodedCartesian3();
this._cameraPosition = new Cartesian3();
this._sunPositionWC = new Cartesian3();
this._sunPositionColumbusView = new Cartesian3();
this._sunDirectionWC = new Cartesian3();
this._sunDirectionEC = new Cartesian3();
this._moonDirectionEC = new Cartesian3();
this._pass = undefined;
this._mode = undefined;
this._mapProjection = undefined;
this._cameraDirection = new Cartesian3();
this._cameraRight = new Cartesian3();
this._cameraUp = new Cartesian3();
this._frustum2DWidth = 0.0;
this._eyeHeight2D = new Cartesian2();
this._resolutionScale = 1.0;
this._fogDensity = undefined;
}
defineProperties(UniformState.prototype, {
/**
* @memberof UniformState.prototype
* @type {FrameState}
* @readonly
*/
frameState : {
get : function() {
return this._frameState;
}
},
/**
* @memberof UniformState.prototype
* @type {BoundingRectangle}
*/
viewport : {
get : function() {
return this._viewport;
},
set : function(viewport) {
if (!BoundingRectangle.equals(viewport, this._viewport)) {
BoundingRectangle.clone(viewport, this._viewport);
var v = this._viewport;
var vc = this._viewportCartesian4;
vc.x = v.x;
vc.y = v.y;
vc.z = v.width;
vc.w = v.height;
this._viewportDirty = true;
}
}
},
/**
* @memberof UniformState.prototype
* @private
*/
viewportCartesian4 : {
get : function() {
return this._viewportCartesian4;
}
},
viewportOrthographic : {
get : function() {
cleanViewport(this);
return this._viewportOrthographicMatrix;
}
},
viewportTransformation : {
get : function() {
cleanViewport(this);
return this._viewportTransformation;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
model : {
get : function() {
return this._model;
},
set : function(matrix) {
Matrix4.clone(matrix, this._model);
this._modelView3DDirty = true;
this._inverseModelView3DDirty = true;
this._inverseModelDirty = true;
this._inverseTransposeModelDirty = true;
this._modelViewDirty = true;
this._inverseModelViewDirty = true;
this._modelViewRelativeToEyeDirty = true;
this._inverseModelViewDirty = true;
this._modelViewProjectionDirty = true;
this._inverseModelViewProjectionDirty = true;
this._modelViewProjectionRelativeToEyeDirty = true;
this._modelViewInfiniteProjectionDirty = true;
this._normalDirty = true;
this._inverseNormalDirty = true;
this._normal3DDirty = true;
this._inverseNormal3DDirty = true;
this._encodedCameraPositionMCDirty = true;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseModel : {
get : function() {
if (this._inverseModelDirty) {
this._inverseModelDirty = false;
Matrix4.inverse(this._model, this._inverseModel);
}
return this._inverseModel;
}
},
/**
* @memberof UniformState.prototype
* @private
*/
inverseTransposeModel : {
get : function() {
var m = this._inverseTransposeModel;
if (this._inverseTransposeModelDirty) {
this._inverseTransposeModelDirty = false;
Matrix4.getRotation(this.inverseModel, m);
Matrix3.transpose(m, m);
}
return m;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
view : {
get : function() {
return this._view;
}
},
/**
* The 3D view matrix. In 3D mode, this is identical to {@link UniformState#view},
* but in 2D and Columbus View it is a synthetic matrix based on the equivalent position
* of the camera in the 3D world.
* @memberof UniformState.prototype
* @type {Matrix4}
*/
view3D : {
get : function() {
updateView3D(this);
return this._view3D;
}
},
/**
* The 3x3 rotation matrix of the current view matrix ({@link UniformState#view}).
* @memberof UniformState.prototype
* @type {Matrix3}
*/
viewRotation : {
get : function() {
updateView3D(this);
return this._viewRotation;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix3}
*/
viewRotation3D : {
get : function() {
updateView3D(this);
return this._viewRotation3D;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseView : {
get : function() {
return this._inverseView;
}
},
/**
* the 4x4 inverse-view matrix that transforms from eye to 3D world coordinates. In 3D mode, this is
* identical to {@link UniformState#inverseView}, but in 2D and Columbus View it is a synthetic matrix
* based on the equivalent position of the camera in the 3D world.
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseView3D : {
get : function() {
updateInverseView3D(this);
return this._inverseView3D;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix3}
*/
inverseViewRotation : {
get : function() {
return this._inverseViewRotation;
}
},
/**
* The 3x3 rotation matrix of the current 3D inverse-view matrix ({@link UniformState#inverseView3D}).
* @memberof UniformState.prototype
* @type {Matrix3}
*/
inverseViewRotation3D : {
get : function() {
updateInverseView3D(this);
return this._inverseViewRotation3D;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
projection : {
get : function() {
return this._projection;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseProjection : {
get : function() {
cleanInverseProjection(this);
return this._inverseProjection;
}
},
/**
* @memberof UniformState.prototype
* @private
*/
inverseProjectionOIT : {
get : function() {
cleanInverseProjectionOIT(this);
return this._inverseProjectionOIT;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
infiniteProjection : {
get : function() {
return this._infiniteProjection;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
modelView : {
get : function() {
cleanModelView(this);
return this._modelView;
}
},
/**
* The 3D model-view matrix. In 3D mode, this is equivalent to {@link UniformState#modelView}. In 2D and
* Columbus View, however, it is a synthetic matrix based on the equivalent position of the camera in the 3D world.
* @memberof UniformState.prototype
* @type {Matrix4}
*/
modelView3D : {
get : function() {
cleanModelView3D(this);
return this._modelView3D;
}
},
/**
* Model-view relative to eye matrix.
*
* @memberof UniformState.prototype
* @type {Matrix4}
*/
modelViewRelativeToEye : {
get : function() {
cleanModelViewRelativeToEye(this);
return this._modelViewRelativeToEye;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseModelView : {
get : function() {
cleanInverseModelView(this);
return this._inverseModelView;
}
},
/**
* The inverse of the 3D model-view matrix. In 3D mode, this is equivalent to {@link UniformState#inverseModelView}.
* In 2D and Columbus View, however, it is a synthetic matrix based on the equivalent position of the camera in the 3D world.
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseModelView3D : {
get : function() {
cleanInverseModelView3D(this);
return this._inverseModelView3D;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
viewProjection : {
get : function() {
cleanViewProjection(this);
return this._viewProjection;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseViewProjection : {
get : function() {
cleanInverseViewProjection(this);
return this._inverseViewProjection;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
modelViewProjection : {
get : function() {
cleanModelViewProjection(this);
return this._modelViewProjection;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
inverseModelViewProjection : {
get : function() {
cleanInverseModelViewProjection(this);
return this._inverseModelViewProjection;
}
},
/**
* Model-view-projection relative to eye matrix.
*
* @memberof UniformState.prototype
* @type {Matrix4}
*/
modelViewProjectionRelativeToEye : {
get : function() {
cleanModelViewProjectionRelativeToEye(this);
return this._modelViewProjectionRelativeToEye;
}
},
/**
* @memberof UniformState.prototype
* @type {Matrix4}
*/
modelViewInfiniteProjection : {
get : function() {
cleanModelViewInfiniteProjection(this);
return this._modelViewInfiniteProjection;
}
},
/**
* A 3x3 normal transformation matrix that transforms normal vectors in model coordinates to
* eye coordinates.
* @memberof UniformState.prototype
* @type {Matrix3}
*/
normal : {
get : function() {
cleanNormal(this);
return this._normal;
}
},
/**
* A 3x3 normal transformation matrix that transforms normal vectors in 3D model
* coordinates to eye coordinates. In 3D mode, this is identical to
* {@link UniformState#normal}, but in 2D and Columbus View it represents the normal transformation
* matrix as if the camera were at an equivalent location in 3D mode.
* @memberof UniformState.prototype
* @type {Matrix3}
*/
normal3D : {
get : function() {
cleanNormal3D(this);
return this._normal3D;
}
},
/**
* An inverse 3x3 normal transformation matrix that transforms normal vectors in model coordinates
* to eye coordinates.
* @memberof UniformState.prototype
* @type {Matrix3}
*/
inverseNormal : {
get : function() {
cleanInverseNormal(this);
return this._inverseNormal;
}
},
/**
* An inverse 3x3 normal transformation matrix that transforms normal vectors in eye coordinates
* to 3D model coordinates. In 3D mode, this is identical to
* {@link UniformState#inverseNormal}, but in 2D and Columbus View it represents the normal transformation
* matrix as if the camera were at an equivalent location in 3D mode.
* @memberof UniformState.prototype
* @type {Matrix3}
*/
inverseNormal3D : {
get : function() {
cleanInverseNormal3D(this);
return this._inverseNormal3D;
}
},
/**
* The near distance (x
) and the far distance (y
) of the frustum defined by the camera.
* This is the largest possible frustum, not an individual frustum used for multi-frustum rendering.
* @memberof UniformState.prototype
* @type {Cartesian2}
*/
entireFrustum : {
get : function() {
return this._entireFrustum;
}
},
/**
* The near distance (x
) and the far distance (y
) of the frustum defined by the camera.
* This is the individual frustum used for multi-frustum rendering.
* @memberof UniformState.prototype
* @type {Cartesian2}
*/
currentFrustum : {
get : function() {
return this._currentFrustum;
}
},
/**
* The distances to the frustum planes. The top, bottom, left and right distances are
* the x, y, z, and w components, respectively.
* @memberof UniformState.prototype
* @type {Cartesian4}
*/
frustumPlanes : {
get : function() {
return this._frustumPlanes;
}
},
/**
* The the height (x
) and the height squared (y
)
* in meters of the camera above the 2D world plane. This uniform is only valid
* when the {@link SceneMode} equal to SCENE2D
.
* @memberof UniformState.prototype
* @type {Cartesian2}
*/
eyeHeight2D : {
get : function() {
return this._eyeHeight2D;
}
},
/**
* The sun position in 3D world coordinates at the current scene time.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
sunPositionWC : {
get : function() {
return this._sunPositionWC;
}
},
/**
* The sun position in 2D world coordinates at the current scene time.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
sunPositionColumbusView : {
get : function(){
return this._sunPositionColumbusView;
}
},
/**
* A normalized vector to the sun in 3D world coordinates at the current scene time. Even in 2D or
* Columbus View mode, this returns the position of the sun in the 3D scene.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
sunDirectionWC : {
get : function() {
return this._sunDirectionWC;
}
},
/**
* A normalized vector to the sun in eye coordinates at the current scene time. In 3D mode, this
* returns the actual vector from the camera position to the sun position. In 2D and Columbus View, it returns
* the vector from the equivalent 3D camera position to the position of the sun in the 3D scene.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
sunDirectionEC : {
get : function() {
return this._sunDirectionEC;
}
},
/**
* A normalized vector to the moon in eye coordinates at the current scene time. In 3D mode, this
* returns the actual vector from the camera position to the moon position. In 2D and Columbus View, it returns
* the vector from the equivalent 3D camera position to the position of the moon in the 3D scene.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
moonDirectionEC : {
get : function() {
return this._moonDirectionEC;
}
},
/**
* The high bits of the camera position.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
encodedCameraPositionMCHigh : {
get : function() {
cleanEncodedCameraPositionMC(this);
return this._encodedCameraPositionMC.high;
}
},
/**
* The low bits of the camera position.
* @memberof UniformState.prototype
* @type {Cartesian3}
*/
encodedCameraPositionMCLow : {
get : function() {
cleanEncodedCameraPositionMC(this);
return this._encodedCameraPositionMC.low;
}
},
/**
* A 3x3 matrix that transforms from True Equator Mean Equinox (TEME) axes to the
* pseudo-fixed axes at the Scene's current time.
* @memberof UniformState.prototype
* @type {Matrix3}
*/
temeToPseudoFixedMatrix : {
get : function() {
return this._temeToPseudoFixed;
}
},
/**
* Gets the scaling factor for transforming from the canvas
* pixel space to canvas coordinate space.
* @memberof UniformState.prototype
* @type {Number}
*/
resolutionScale : {
get : function() {
return this._resolutionScale;
}
},
/**
* A scalar used to mix a color with the fog color based on the distance to the camera.
* @memberof UniformState.prototype
* @type {Number}
*/
fogDensity : {
get : function() {
return this._fogDensity;
}
},
/**
* @memberof UniformState.prototype
* @type {Pass}
*/
pass : {
get : function() {
return this._pass;
}
}
});
function setView(uniformState, matrix) {
Matrix4.clone(matrix, uniformState._view);
Matrix4.getRotation(matrix, uniformState._viewRotation);
uniformState._view3DDirty = true;
uniformState._inverseView3DDirty = true;
uniformState._modelViewDirty = true;
uniformState._modelView3DDirty = true;
uniformState._modelViewRelativeToEyeDirty = true;
uniformState._inverseModelViewDirty = true;
uniformState._inverseModelView3DDirty = true;
uniformState._viewProjectionDirty = true;
uniformState._inverseViewProjectionDirty = true;
uniformState._modelViewProjectionDirty = true;
uniformState._modelViewProjectionRelativeToEyeDirty = true;
uniformState._modelViewInfiniteProjectionDirty = true;
uniformState._normalDirty = true;
uniformState._inverseNormalDirty = true;
uniformState._normal3DDirty = true;
uniformState._inverseNormal3DDirty = true;
}
function setInverseView(uniformState, matrix) {
Matrix4.clone(matrix, uniformState._inverseView);
Matrix4.getRotation(matrix, uniformState._inverseViewRotation);
}
function setProjection(uniformState, matrix) {
Matrix4.clone(matrix, uniformState._projection);
uniformState._inverseProjectionDirty = true;
uniformState._inverseProjectionOITDirty = true;
uniformState._viewProjectionDirty = true;
uniformState._inverseViewProjectionDirty = true;
uniformState._modelViewProjectionDirty = true;
uniformState._modelViewProjectionRelativeToEyeDirty = true;
}
function setInfiniteProjection(uniformState, matrix) {
Matrix4.clone(matrix, uniformState._infiniteProjection);
uniformState._modelViewInfiniteProjectionDirty = true;
}
function setCamera(uniformState, camera) {
Cartesian3.clone(camera.positionWC, uniformState._cameraPosition);
Cartesian3.clone(camera.directionWC, uniformState._cameraDirection);
Cartesian3.clone(camera.rightWC, uniformState._cameraRight);
Cartesian3.clone(camera.upWC, uniformState._cameraUp);
uniformState._encodedCameraPositionMCDirty = true;
}
var transformMatrix = new Matrix3();
var sunCartographicScratch = new Cartographic();
function setSunAndMoonDirections(uniformState, frameState) {
if (!defined(Transforms.computeIcrfToFixedMatrix(frameState.time, transformMatrix))) {
transformMatrix = Transforms.computeTemeToPseudoFixedMatrix(frameState.time, transformMatrix);
}
var position = Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(frameState.time, uniformState._sunPositionWC);
Matrix3.multiplyByVector(transformMatrix, position, position);
Cartesian3.normalize(position, uniformState._sunDirectionWC);
position = Matrix3.multiplyByVector(uniformState.viewRotation3D, position, uniformState._sunDirectionEC);
Cartesian3.normalize(position, position);
position = Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame(frameState.time, uniformState._moonDirectionEC);
Matrix3.multiplyByVector(transformMatrix, position, position);
Matrix3.multiplyByVector(uniformState.viewRotation3D, position, position);
Cartesian3.normalize(position, position);
var projection = frameState.mapProjection;
var ellipsoid = projection.ellipsoid;
var sunCartographic = ellipsoid.cartesianToCartographic(uniformState._sunPositionWC, sunCartographicScratch);
projection.project(sunCartographic, uniformState._sunPositionColumbusView);
}
/**
* Synchronizes the frustum's state with the camera state. This is called
* by the {@link Scene} when rendering to ensure that automatic GLSL uniforms
* are set to the right value.
*
* @param {Object} camera The camera to synchronize with.
*/
UniformState.prototype.updateCamera = function(camera) {
setView(this, camera.viewMatrix);
setInverseView(this, camera.inverseViewMatrix);
setCamera(this, camera);
this._entireFrustum.x = camera.frustum.near;
this._entireFrustum.y = camera.frustum.far;
this.updateFrustum(camera.frustum);
};
/**
* Synchronizes the frustum's state with the uniform state. This is called
* by the {@link Scene} when rendering to ensure that automatic GLSL uniforms
* are set to the right value.
*
* @param {Object} frustum The frustum to synchronize with.
*/
UniformState.prototype.updateFrustum = function(frustum) {
setProjection(this, frustum.projectionMatrix);
if (defined(frustum.infiniteProjectionMatrix)) {
setInfiniteProjection(this, frustum.infiniteProjectionMatrix);
}
this._currentFrustum.x = frustum.near;
this._currentFrustum.y = frustum.far;
if (!defined(frustum.top)) {
frustum = frustum._offCenterFrustum;
}
this._frustumPlanes.x = frustum.top;
this._frustumPlanes.y = frustum.bottom;
this._frustumPlanes.z = frustum.left;
this._frustumPlanes.w = frustum.right;
};
UniformState.prototype.updatePass = function(pass) {
this._pass = pass;
};
/**
* Synchronizes frame state with the uniform state. This is called
* by the {@link Scene} when rendering to ensure that automatic GLSL uniforms
* are set to the right value.
*
* @param {FrameState} frameState The frameState to synchronize with.
*/
UniformState.prototype.update = function(frameState) {
this._mode = frameState.mode;
this._mapProjection = frameState.mapProjection;
var canvas = frameState.context._canvas;
this._resolutionScale = canvas.width / canvas.clientWidth;
var camera = frameState.camera;
this.updateCamera(camera);
if (frameState.mode === SceneMode.SCENE2D) {
this._frustum2DWidth = camera.frustum.right - camera.frustum.left;
this._eyeHeight2D.x = this._frustum2DWidth * 0.5;
this._eyeHeight2D.y = this._eyeHeight2D.x * this._eyeHeight2D.x;
} else {
this._frustum2DWidth = 0.0;
this._eyeHeight2D.x = 0.0;
this._eyeHeight2D.y = 0.0;
}
setSunAndMoonDirections(this, frameState);
this._fogDensity = frameState.fog.density;
this._frameState = frameState;
this._temeToPseudoFixed = Transforms.computeTemeToPseudoFixedMatrix(frameState.time, this._temeToPseudoFixed);
};
function cleanViewport(uniformState) {
if (uniformState._viewportDirty) {
var v = uniformState._viewport;
Matrix4.computeOrthographicOffCenter(v.x, v.x + v.width, v.y, v.y + v.height, 0.0, 1.0, uniformState._viewportOrthographicMatrix);
Matrix4.computeViewportTransformation(v, 0.0, 1.0, uniformState._viewportTransformation);
uniformState._viewportDirty = false;
}
}
function cleanInverseProjection(uniformState) {
if (uniformState._inverseProjectionDirty) {
uniformState._inverseProjectionDirty = false;
Matrix4.inverse(uniformState._projection, uniformState._inverseProjection);
}
}
function cleanInverseProjectionOIT(uniformState) {
if (uniformState._inverseProjectionOITDirty) {
uniformState._inverseProjectionOITDirty = false;
if (uniformState._mode !== SceneMode.SCENE2D && uniformState._mode !== SceneMode.MORPHING) {
Matrix4.inverse(uniformState._projection, uniformState._inverseProjectionOIT);
} else {
Matrix4.clone(Matrix4.IDENTITY, uniformState._inverseProjectionOIT);
}
}
}
// Derived
function cleanModelView(uniformState) {
if (uniformState._modelViewDirty) {
uniformState._modelViewDirty = false;
Matrix4.multiplyTransformation(uniformState._view, uniformState._model, uniformState._modelView);
}
}
function cleanModelView3D(uniformState) {
if (uniformState._modelView3DDirty) {
uniformState._modelView3DDirty = false;
Matrix4.multiplyTransformation(uniformState.view3D, uniformState._model, uniformState._modelView3D);
}
}
function cleanInverseModelView(uniformState) {
if (uniformState._inverseModelViewDirty) {
uniformState._inverseModelViewDirty = false;
Matrix4.inverse(uniformState.modelView, uniformState._inverseModelView);
}
}
function cleanInverseModelView3D(uniformState) {
if (uniformState._inverseModelView3DDirty) {
uniformState._inverseModelView3DDirty = false;
Matrix4.inverse(uniformState.modelView3D, uniformState._inverseModelView3D);
}
}
function cleanViewProjection(uniformState) {
if (uniformState._viewProjectionDirty) {
uniformState._viewProjectionDirty = false;
Matrix4.multiply(uniformState._projection, uniformState._view, uniformState._viewProjection);
}
}
function cleanInverseViewProjection(uniformState) {
if (uniformState._inverseViewProjectionDirty) {
uniformState._inverseViewProjectionDirty = false;
Matrix4.inverse(uniformState.viewProjection, uniformState._inverseViewProjection);
}
}
function cleanModelViewProjection(uniformState) {
if (uniformState._modelViewProjectionDirty) {
uniformState._modelViewProjectionDirty = false;
Matrix4.multiply(uniformState._projection, uniformState.modelView, uniformState._modelViewProjection);
}
}
function cleanModelViewRelativeToEye(uniformState) {
if (uniformState._modelViewRelativeToEyeDirty) {
uniformState._modelViewRelativeToEyeDirty = false;
var mv = uniformState.modelView;
var mvRte = uniformState._modelViewRelativeToEye;
mvRte[0] = mv[0];
mvRte[1] = mv[1];
mvRte[2] = mv[2];
mvRte[3] = mv[3];
mvRte[4] = mv[4];
mvRte[5] = mv[5];
mvRte[6] = mv[6];
mvRte[7] = mv[7];
mvRte[8] = mv[8];
mvRte[9] = mv[9];
mvRte[10] = mv[10];
mvRte[11] = mv[11];
mvRte[12] = 0.0;
mvRte[13] = 0.0;
mvRte[14] = 0.0;
mvRte[15] = mv[15];
}
}
function cleanInverseModelViewProjection(uniformState) {
if (uniformState._inverseModelViewProjectionDirty) {
uniformState._inverseModelViewProjectionDirty = false;
Matrix4.inverse(uniformState.modelViewProjection, uniformState._inverseModelViewProjection);
}
}
function cleanModelViewProjectionRelativeToEye(uniformState) {
if (uniformState._modelViewProjectionRelativeToEyeDirty) {
uniformState._modelViewProjectionRelativeToEyeDirty = false;
Matrix4.multiply(uniformState._projection, uniformState.modelViewRelativeToEye, uniformState._modelViewProjectionRelativeToEye);
}
}
function cleanModelViewInfiniteProjection(uniformState) {
if (uniformState._modelViewInfiniteProjectionDirty) {
uniformState._modelViewInfiniteProjectionDirty = false;
Matrix4.multiply(uniformState._infiniteProjection, uniformState.modelView, uniformState._modelViewInfiniteProjection);
}
}
function cleanNormal(uniformState) {
if (uniformState._normalDirty) {
uniformState._normalDirty = false;
var m = uniformState._normal;
Matrix4.getRotation(uniformState.inverseModelView, m);
Matrix3.transpose(m, m);
}
}
function cleanNormal3D(uniformState) {
if (uniformState._normal3DDirty) {
uniformState._normal3DDirty = false;
var m = uniformState._normal3D;
Matrix4.getRotation(uniformState.inverseModelView3D, m);
Matrix3.transpose(m, m);
}
}
function cleanInverseNormal(uniformState) {
if (uniformState._inverseNormalDirty) {
uniformState._inverseNormalDirty = false;
Matrix4.getRotation(uniformState.inverseModelView, uniformState._inverseNormal);
}
}
function cleanInverseNormal3D(uniformState) {
if (uniformState._inverseNormal3DDirty) {
uniformState._inverseNormal3DDirty = false;
Matrix4.getRotation(uniformState.inverseModelView3D, uniformState._inverseNormal3D);
}
}
var cameraPositionMC = new Cartesian3();
function cleanEncodedCameraPositionMC(uniformState) {
if (uniformState._encodedCameraPositionMCDirty) {
uniformState._encodedCameraPositionMCDirty = false;
Matrix4.multiplyByPoint(uniformState.inverseModel, uniformState._cameraPosition, cameraPositionMC);
EncodedCartesian3.fromCartesian(cameraPositionMC, uniformState._encodedCameraPositionMC);
}
}
var view2Dto3DPScratch = new Cartesian3();
var view2Dto3DRScratch = new Cartesian3();
var view2Dto3DUScratch = new Cartesian3();
var view2Dto3DDScratch = new Cartesian3();
var view2Dto3DCartographicScratch = new Cartographic();
var view2Dto3DCartesian3Scratch = new Cartesian3();
var view2Dto3DMatrix4Scratch = new Matrix4();
function view2Dto3D(position2D, direction2D, right2D, up2D, frustum2DWidth, mode, projection, result) {
// The camera position and directions are expressed in the 2D coordinate system where the Y axis is to the East,
// the Z axis is to the North, and the X axis is out of the map. Express them instead in the ENU axes where
// X is to the East, Y is to the North, and Z is out of the local horizontal plane.
var p = view2Dto3DPScratch;
p.x = position2D.y;
p.y = position2D.z;
p.z = position2D.x;
var r = view2Dto3DRScratch;
r.x = right2D.y;
r.y = right2D.z;
r.z = right2D.x;
var u = view2Dto3DUScratch;
u.x = up2D.y;
u.y = up2D.z;
u.z = up2D.x;
var d = view2Dto3DDScratch;
d.x = direction2D.y;
d.y = direction2D.z;
d.z = direction2D.x;
// In 2D, the camera height is always 12.7 million meters.
// The apparent height is equal to half the frustum width.
if (mode === SceneMode.SCENE2D) {
p.z = frustum2DWidth * 0.5;
}
// Compute the equivalent camera position in the real (3D) world.
// In 2D and Columbus View, the camera can travel outside the projection, and when it does so
// there's not really any corresponding location in the real world. So clamp the unprojected
// longitude and latitude to their valid ranges.
var cartographic = projection.unproject(p, view2Dto3DCartographicScratch);
cartographic.longitude = CesiumMath.clamp(cartographic.longitude, -Math.PI, Math.PI);
cartographic.latitude = CesiumMath.clamp(cartographic.latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO);
var ellipsoid = projection.ellipsoid;
var position3D = ellipsoid.cartographicToCartesian(cartographic, view2Dto3DCartesian3Scratch);
// Compute the rotation from the local ENU at the real world camera position to the fixed axes.
var enuToFixed = Transforms.eastNorthUpToFixedFrame(position3D, ellipsoid, view2Dto3DMatrix4Scratch);
// Transform each camera direction to the fixed axes.
Matrix4.multiplyByPointAsVector(enuToFixed, r, r);
Matrix4.multiplyByPointAsVector(enuToFixed, u, u);
Matrix4.multiplyByPointAsVector(enuToFixed, d, d);
// Compute the view matrix based on the new fixed-frame camera position and directions.
if (!defined(result)) {
result = new Matrix4();
}
result[0] = r.x;
result[1] = u.x;
result[2] = -d.x;
result[3] = 0.0;
result[4] = r.y;
result[5] = u.y;
result[6] = -d.y;
result[7] = 0.0;
result[8] = r.z;
result[9] = u.z;
result[10] = -d.z;
result[11] = 0.0;
result[12] = -Cartesian3.dot(r, position3D);
result[13] = -Cartesian3.dot(u, position3D);
result[14] = Cartesian3.dot(d, position3D);
result[15] = 1.0;
return result;
}
function updateView3D(that) {
if (that._view3DDirty) {
if (that._mode === SceneMode.SCENE3D) {
Matrix4.clone(that._view, that._view3D);
} else {
view2Dto3D(that._cameraPosition, that._cameraDirection, that._cameraRight, that._cameraUp, that._frustum2DWidth, that._mode, that._mapProjection, that._view3D);
}
Matrix4.getRotation(that._view3D, that._viewRotation3D);
that._view3DDirty = false;
}
}
function updateInverseView3D(that){
if (that._inverseView3DDirty) {
Matrix4.inverseTransformation(that.view3D, that._inverseView3D);
Matrix4.getRotation(that._inverseView3D, that._inverseViewRotation3D);
that._inverseView3DDirty = false;
}
}
return UniformState;
});
/*global define*/
define('Renderer/Context',[
'../Core/clone',
'../Core/Color',
'../Core/ComponentDatatype',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Geometry',
'../Core/GeometryAttribute',
'../Core/Matrix4',
'../Core/PrimitiveType',
'../Core/RuntimeError',
'../Core/WebGLConstants',
'../Shaders/ViewportQuadVS',
'./BufferUsage',
'./ClearCommand',
'./ContextLimits',
'./CubeMap',
'./DrawCommand',
'./PassState',
'./PickFramebuffer',
'./RenderState',
'./ShaderCache',
'./ShaderProgram',
'./Texture',
'./UniformState',
'./VertexArray'
], function(
clone,
Color,
ComponentDatatype,
createGuid,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Geometry,
GeometryAttribute,
Matrix4,
PrimitiveType,
RuntimeError,
WebGLConstants,
ViewportQuadVS,
BufferUsage,
ClearCommand,
ContextLimits,
CubeMap,
DrawCommand,
PassState,
PickFramebuffer,
RenderState,
ShaderCache,
ShaderProgram,
Texture,
UniformState,
VertexArray) {
'use strict';
/*global WebGLRenderingContext*/
/*global WebGL2RenderingContext*/
function errorToString(gl, error) {
var message = 'WebGL Error: ';
switch (error) {
case gl.INVALID_ENUM:
message += 'INVALID_ENUM';
break;
case gl.INVALID_VALUE:
message += 'INVALID_VALUE';
break;
case gl.INVALID_OPERATION:
message += 'INVALID_OPERATION';
break;
case gl.OUT_OF_MEMORY:
message += 'OUT_OF_MEMORY';
break;
case gl.CONTEXT_LOST_WEBGL:
message += 'CONTEXT_LOST_WEBGL lost';
break;
default:
message += 'Unknown (' + error + ')';
}
return message;
}
function createErrorMessage(gl, glFunc, glFuncArguments, error) {
var message = errorToString(gl, error) + ': ' + glFunc.name + '(';
for ( var i = 0; i < glFuncArguments.length; ++i) {
if (i !== 0) {
message += ', ';
}
message += glFuncArguments[i];
}
message += ');';
return message;
}
function throwOnError(gl, glFunc, glFuncArguments) {
var error = gl.getError();
if (error !== gl.NO_ERROR) {
throw new RuntimeError(createErrorMessage(gl, glFunc, glFuncArguments, error));
}
}
function makeGetterSetter(gl, propertyName, logFunc) {
return {
get : function() {
var value = gl[propertyName];
logFunc(gl, 'get: ' + propertyName, value);
return gl[propertyName];
},
set : function(value) {
gl[propertyName] = value;
logFunc(gl, 'set: ' + propertyName, value);
}
};
}
function wrapGL(gl, logFunc) {
if (!logFunc) {
return gl;
}
function wrapFunction(property) {
return function() {
var result = property.apply(gl, arguments);
logFunc(gl, property, arguments);
return result;
};
}
var glWrapper = {};
/*jslint forin: true*/
/*jshint forin: false*/
// JSLint normally demands that a for..in loop must directly contain an if,
// but in our loop below, we actually intend to iterate all properties, including
// those in the prototype.
for ( var propertyName in gl) {
var property = gl[propertyName];
// wrap any functions we encounter, otherwise just copy the property to the wrapper.
if (typeof property === 'function') {
glWrapper[propertyName] = wrapFunction(property);
} else {
Object.defineProperty(glWrapper, propertyName, makeGetterSetter(gl, propertyName, logFunc));
}
}
return glWrapper;
}
function getExtension(gl, names) {
var length = names.length;
for (var i = 0; i < length; ++i) {
var extension = gl.getExtension(names[i]);
if (extension) {
return extension;
}
}
return undefined;
}
/**
* @private
*/
function Context(canvas, options) {
// this check must use typeof, not defined, because defined doesn't work with undeclared variables.
if (typeof WebGLRenderingContext === 'undefined') {
throw new RuntimeError('The browser does not support WebGL. Visit http://get.webgl.org.');
}
if (!defined(canvas)) {
throw new DeveloperError('canvas is required.');
}
this._canvas = canvas;
options = clone(options, true);
options = defaultValue(options, {});
options.allowTextureFilterAnisotropic = defaultValue(options.allowTextureFilterAnisotropic, true);
var webglOptions = defaultValue(options.webgl, {});
// Override select WebGL defaults
webglOptions.alpha = defaultValue(webglOptions.alpha, false); // WebGL default is true
webglOptions.stencil = defaultValue(webglOptions.stencil, true); // WebGL default is false
var defaultToWebgl2 = false;
var webgl2Supported = (typeof WebGL2RenderingContext !== 'undefined');
var webgl2 = false;
var glContext;
if (defaultToWebgl2 && webgl2Supported) {
glContext = canvas.getContext('webgl2', webglOptions) || canvas.getContext('experimental-webgl2', webglOptions) || undefined;
if (defined(glContext)) {
webgl2 = true;
}
}
if (!defined(glContext)) {
glContext = canvas.getContext('webgl', webglOptions) || canvas.getContext('experimental-webgl', webglOptions) || undefined;
}
if (!defined(glContext)) {
throw new RuntimeError('The browser supports WebGL, but initialization failed.');
}
this._originalGLContext = glContext;
this._webgl2 = webgl2;
this._id = createGuid();
// Validation and logging disabled by default for speed.
this.validateFramebuffer = false;
this.validateShaderProgram = false;
this.logShaderCompilation = false;
this._throwOnWebGLError = false;
this._shaderCache = new ShaderCache(this);
var gl = this._gl = this._originalGLContext;
this._redBits = gl.getParameter(gl.RED_BITS);
this._greenBits = gl.getParameter(gl.GREEN_BITS);
this._blueBits = gl.getParameter(gl.BLUE_BITS);
this._alphaBits = gl.getParameter(gl.ALPHA_BITS);
this._depthBits = gl.getParameter(gl.DEPTH_BITS);
this._stencilBits = gl.getParameter(gl.STENCIL_BITS);
ContextLimits._maximumCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); // min: 8
ContextLimits._maximumCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); // min: 16
ContextLimits._maximumFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); // min: 16
ContextLimits._maximumTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); // min: 8
ContextLimits._maximumRenderbufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE); // min: 1
ContextLimits._maximumTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); // min: 64
ContextLimits._maximumVaryingVectors = gl.getParameter(gl.MAX_VARYING_VECTORS); // min: 8
ContextLimits._maximumVertexAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); // min: 8
ContextLimits._maximumVertexTextureImageUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); // min: 0
ContextLimits._maximumVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS); // min: 128
var aliasedLineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); // must include 1
ContextLimits._minimumAliasedLineWidth = aliasedLineWidthRange[0];
ContextLimits._maximumAliasedLineWidth = aliasedLineWidthRange[1];
var aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE); // must include 1
ContextLimits._minimumAliasedPointSize = aliasedPointSizeRange[0];
ContextLimits._maximumAliasedPointSize = aliasedPointSizeRange[1];
var maximumViewportDimensions = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
ContextLimits._maximumViewportWidth = maximumViewportDimensions[0];
ContextLimits._maximumViewportHeight = maximumViewportDimensions[1];
var highpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
ContextLimits._highpFloatSupported = highpFloat.precision !== 0;
var highpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
ContextLimits._highpIntSupported = highpInt.rangeMax !== 0;
this._antialias = gl.getContextAttributes().antialias;
// Query and initialize extensions
this._standardDerivatives = !!getExtension(gl, ['OES_standard_derivatives']);
this._elementIndexUint = !!getExtension(gl, ['OES_element_index_uint']);
this._depthTexture = !!getExtension(gl, ['WEBGL_depth_texture', 'WEBKIT_WEBGL_depth_texture']);
this._textureFloat = !!getExtension(gl, ['OES_texture_float']);
this._fragDepth = !!getExtension(gl, ['EXT_frag_depth']);
this._debugShaders = getExtension(gl, ['WEBGL_debug_shaders']);
var textureFilterAnisotropic = options.allowTextureFilterAnisotropic ? getExtension(gl, ['EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic']) : undefined;
this._textureFilterAnisotropic = textureFilterAnisotropic;
ContextLimits._maximumTextureFilterAnisotropy = defined(textureFilterAnisotropic) ? gl.getParameter(textureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1.0;
var glCreateVertexArray;
var glBindVertexArray;
var glDeleteVertexArray;
var glDrawElementsInstanced;
var glDrawArraysInstanced;
var glVertexAttribDivisor;
var glDrawBuffers;
var vertexArrayObject;
var instancedArrays;
var drawBuffers;
if (webgl2) {
var that = this;
glCreateVertexArray = function () { return that._gl.createVertexArray(); };
glBindVertexArray = function(vao) { that._gl.bindVertexArray(vao); };
glDeleteVertexArray = function(vao) { that._gl.deleteVertexArray(vao); };
glDrawElementsInstanced = function(mode, count, type, offset, instanceCount) { gl.drawElementsInstanced(mode, count, type, offset, instanceCount); };
glDrawArraysInstanced = function(mode, first, count, instanceCount) { gl.drawArraysInstanced(mode, first, count, instanceCount); };
glVertexAttribDivisor = function(index, divisor) { gl.vertexAttribDivisor(index, divisor); };
glDrawBuffers = function(buffers) { gl.drawBuffers(buffers); };
} else {
vertexArrayObject = getExtension(gl, ['OES_vertex_array_object']);
if (defined(vertexArrayObject)) {
glCreateVertexArray = function() { return vertexArrayObject.createVertexArrayOES(); };
glBindVertexArray = function(vertexArray) { vertexArrayObject.bindVertexArrayOES(vertexArray); };
glDeleteVertexArray = function(vertexArray) { vertexArrayObject.deleteVertexArrayOES(vertexArray); };
}
instancedArrays = getExtension(gl, ['ANGLE_instanced_arrays']);
if (defined(instancedArrays)) {
glDrawElementsInstanced = function(mode, count, type, offset, instanceCount) { instancedArrays.drawElementsInstancedANGLE(mode, count, type, offset, instanceCount); };
glDrawArraysInstanced = function(mode, first, count, instanceCount) { instancedArrays.drawArraysInstancedANGLE(mode, first, count, instanceCount); };
glVertexAttribDivisor = function(index, divisor) { instancedArrays.vertexAttribDivisorANGLE(index, divisor); };
}
drawBuffers = getExtension(gl, ['WEBGL_draw_buffers']);
if (defined(drawBuffers)) {
glDrawBuffers = function(buffers) { drawBuffers.drawBuffersWEBGL(buffers); };
}
}
this.glCreateVertexArray = glCreateVertexArray;
this.glBindVertexArray = glBindVertexArray;
this.glDeleteVertexArray = glDeleteVertexArray;
this.glDrawElementsInstanced = glDrawElementsInstanced;
this.glDrawArraysInstanced = glDrawArraysInstanced;
this.glVertexAttribDivisor = glVertexAttribDivisor;
this.glDrawBuffers = glDrawBuffers;
this._vertexArrayObject = !!vertexArrayObject;
this._instancedArrays = !!instancedArrays;
this._drawBuffers = !!drawBuffers;
ContextLimits._maximumDrawBuffers = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_DRAW_BUFFERS) : 1;
ContextLimits._maximumColorAttachments = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_COLOR_ATTACHMENTS) : 1;
var cc = gl.getParameter(gl.COLOR_CLEAR_VALUE);
this._clearColor = new Color(cc[0], cc[1], cc[2], cc[3]);
this._clearDepth = gl.getParameter(gl.DEPTH_CLEAR_VALUE);
this._clearStencil = gl.getParameter(gl.STENCIL_CLEAR_VALUE);
var us = new UniformState();
var ps = new PassState(this);
var rs = RenderState.fromCache();
this._defaultPassState = ps;
this._defaultRenderState = rs;
this._defaultTexture = undefined;
this._defaultCubeMap = undefined;
this._us = us;
this._currentRenderState = rs;
this._currentPassState = ps;
this._currentFramebuffer = undefined;
this._maxFrameTextureUnitIndex = 0;
// Vertex attribute divisor state cache. Workaround for ANGLE (also look at VertexArray.setVertexAttribDivisor)
this._vertexAttribDivisors = [];
this._previousDrawInstanced = false;
for (var i = 0; i < ContextLimits._maximumVertexAttributes; i++) {
this._vertexAttribDivisors.push(0);
}
this._pickObjects = {};
this._nextPickColor = new Uint32Array(1);
/**
* @example
* {
* webgl : {
* alpha : false,
* depth : true,
* stencil : false,
* antialias : true,
* premultipliedAlpha : true,
* preserveDrawingBuffer : false,
* failIfMajorPerformanceCaveat : true
* },
* allowTextureFilterAnisotropic : true
* }
*/
this.options = options;
/**
* A cache of objects tied to this context. Just before the Context is destroyed,
* destroy
will be invoked on each object in this object literal that has
* such a method. This is useful for caching any objects that might otherwise
* be stored globally, except they're tied to a particular context, and to manage
* their lifetime.
*
* @type {Object}
*/
this.cache = {};
RenderState.apply(gl, rs, ps);
}
var defaultFramebufferMarker = {};
defineProperties(Context.prototype, {
id : {
get : function() {
return this._id;
}
},
webgl2 : {
get : function() {
return this._webgl2;
}
},
canvas : {
get : function() {
return this._canvas;
}
},
shaderCache : {
get : function() {
return this._shaderCache;
}
},
uniformState : {
get : function() {
return this._us;
}
},
/**
* The number of red bits per component in the default framebuffer's color buffer. The minimum is eight.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with RED_BITS
.
*/
redBits : {
get : function() {
return this._redBits;
}
},
/**
* The number of green bits per component in the default framebuffer's color buffer. The minimum is eight.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with GREEN_BITS
.
*/
greenBits : {
get : function() {
return this._greenBits;
}
},
/**
* The number of blue bits per component in the default framebuffer's color buffer. The minimum is eight.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with BLUE_BITS
.
*/
blueBits : {
get : function() {
return this._blueBits;
}
},
/**
* The number of alpha bits per component in the default framebuffer's color buffer. The minimum is eight.
*
* The alpha channel is used for GL destination alpha operations and by the HTML compositor to combine the color buffer
* with the rest of the page.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALPHA_BITS
.
*/
alphaBits : {
get : function() {
return this._alphaBits;
}
},
/**
* The number of depth bits per pixel in the default bound framebuffer. The minimum is 16 bits; most
* implementations will have 24 bits.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with DEPTH_BITS
.
*/
depthBits : {
get : function() {
return this._depthBits;
}
},
/**
* The number of stencil bits per pixel in the default bound framebuffer. The minimum is eight bits.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with STENCIL_BITS
.
*/
stencilBits : {
get : function() {
return this._stencilBits;
}
},
/**
* true
if the WebGL context supports stencil buffers.
* Stencil buffers are not supported by all systems.
* @memberof Context.prototype
* @type {Boolean}
*/
stencilBuffer : {
get : function() {
return this._stencilBits >= 8;
}
},
/**
* true
if the WebGL context supports antialiasing. By default
* antialiasing is requested, but it is not supported by all systems.
* @memberof Context.prototype
* @type {Boolean}
*/
antialias : {
get : function() {
return this._antialias;
}
},
/**
* true
if the OES_standard_derivatives extension is supported. This
* extension provides access to dFdx
, dFdy
, and fwidth
* functions from GLSL. A shader using these functions still needs to explicitly enable the
* extension with #extension GL_OES_standard_derivatives : enable
.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/gles/extensions/OES/OES_standard_derivatives.txt|OES_standard_derivatives}
*/
standardDerivatives : {
get : function() {
return this._standardDerivatives;
}
},
/**
* true
if the OES_element_index_uint extension is supported. This
* extension allows the use of unsigned int indices, which can improve performance by
* eliminating batch breaking caused by unsigned short indices.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/webgl/extensions/OES_element_index_uint/|OES_element_index_uint}
*/
elementIndexUint : {
get : function() {
return this._elementIndexUint || this._webgl2;
}
},
/**
* true
if WEBGL_depth_texture is supported. This extension provides
* access to depth textures that, for example, can be attached to framebuffers for shadow mapping.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/|WEBGL_depth_texture}
*/
depthTexture : {
get : function() {
return this._depthTexture;
}
},
/**
* true
if OES_texture_float is supported. This extension provides
* access to floating point textures that, for example, can be attached to framebuffers for high dynamic range.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/gles/extensions/OES/OES_texture_float.txt|OES_texture_float}
*/
floatingPointTexture : {
get : function() {
return this._textureFloat;
}
},
textureFilterAnisotropic : {
get : function() {
return !!this._textureFilterAnisotropic;
}
},
/**
* true
if the OES_vertex_array_object extension is supported. This
* extension can improve performance by reducing the overhead of switching vertex arrays.
* When enabled, this extension is automatically used by {@link VertexArray}.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/|OES_vertex_array_object}
*/
vertexArrayObject : {
get : function() {
return this._vertexArrayObject || this._webgl2;
}
},
/**
* true
if the EXT_frag_depth extension is supported. This
* extension provides access to the gl_FragDepthEXT
built-in output variable
* from GLSL fragment shaders. A shader using these functions still needs to explicitly enable the
* extension with #extension GL_EXT_frag_depth : enable
.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/webgl/extensions/EXT_frag_depth/|EXT_frag_depth}
*/
fragmentDepth : {
get : function() {
return this._fragDepth;
}
},
/**
* true
if the ANGLE_instanced_arrays extension is supported. This
* extension provides access to instanced rendering.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays}
*/
instancedArrays : {
get : function() {
return this._instancedArrays || this._webgl2;
}
},
/**
* true
if the WEBGL_draw_buffers extension is supported. This
* extensions provides support for multiple render targets. The framebuffer object can have mutiple
* color attachments and the GLSL fragment shader can write to the built-in output array gl_FragData
.
* A shader using this feature needs to explicitly enable the extension with
* #extension GL_EXT_draw_buffers : enable
.
* @memberof Context.prototype
* @type {Boolean}
* @see {@link http://www.khronos.org/registry/webgl/extensions/WEBGL_draw_buffers/|WEBGL_draw_buffers}
*/
drawBuffers : {
get : function() {
return this._drawBuffers || this._webgl2;
}
},
debugShaders : {
get : function() {
return this._debugShaders;
}
},
throwOnWebGLError : {
get : function() {
return this._throwOnWebGLError;
},
set : function(value) {
this._throwOnWebGLError = value;
this._gl = wrapGL(this._originalGLContext, value ? throwOnError : null);
}
},
/**
* A 1x1 RGBA texture initialized to [255, 255, 255, 255]. This can
* be used as a placeholder texture while other textures are downloaded.
* @memberof Context.prototype
* @type {Texture}
*/
defaultTexture : {
get : function() {
if (this._defaultTexture === undefined) {
this._defaultTexture = new Texture({
context : this,
source : {
width : 1,
height : 1,
arrayBufferView : new Uint8Array([255, 255, 255, 255])
}
});
}
return this._defaultTexture;
}
},
/**
* A cube map, where each face is a 1x1 RGBA texture initialized to
* [255, 255, 255, 255]. This can be used as a placeholder cube map while
* other cube maps are downloaded.
* @memberof Context.prototype
* @type {CubeMap}
*/
defaultCubeMap : {
get : function() {
if (this._defaultCubeMap === undefined) {
var face = {
width : 1,
height : 1,
arrayBufferView : new Uint8Array([255, 255, 255, 255])
};
this._defaultCubeMap = new CubeMap({
context : this,
source : {
positiveX : face,
negativeX : face,
positiveY : face,
negativeY : face,
positiveZ : face,
negativeZ : face
}
});
}
return this._defaultCubeMap;
}
},
/**
* The drawingBufferHeight of the underlying GL context.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
*/
drawingBufferHeight : {
get : function() {
return this._gl.drawingBufferHeight;
}
},
/**
* The drawingBufferWidth of the underlying GL context.
* @memberof Context.prototype
* @type {Number}
* @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferWidth|drawingBufferWidth}
*/
drawingBufferWidth : {
get : function() {
return this._gl.drawingBufferWidth;
}
},
/**
* Gets an object representing the currently bound framebuffer. While this instance is not an actual
* {@link Framebuffer}, it is used to represent the default framebuffer in calls to
* {@link Texture.FromFramebuffer}.
* @memberof Context.prototype
* @type {Object}
*/
defaultFramebuffer : {
get : function() {
return defaultFramebufferMarker;
}
}
});
/**
* Validates a framebuffer.
* Available in debug builds only.
* @private
*/
function validateFramebuffer(context) {
if (context.validateFramebuffer) {
var gl = context._gl;
var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
var message;
switch (status) {
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
message = 'Framebuffer is not complete. Incomplete attachment: at least one attachment point with a renderbuffer or texture attached has its attached object no longer in existence or has an attached image with a width or height of zero, or the color attachment point has a non-color-renderable image attached, or the depth attachment point has a non-depth-renderable image attached, or the stencil attachment point has a non-stencil-renderable image attached. Color-renderable formats include GL_RGBA4, GL_RGB5_A1, and GL_RGB565. GL_DEPTH_COMPONENT16 is the only depth-renderable format. GL_STENCIL_INDEX8 is the only stencil-renderable format.';
break;
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
message = 'Framebuffer is not complete. Incomplete dimensions: not all attached images have the same width and height.';
break;
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
message = 'Framebuffer is not complete. Missing attachment: no images are attached to the framebuffer.';
break;
case gl.FRAMEBUFFER_UNSUPPORTED:
message = 'Framebuffer is not complete. Unsupported: the combination of internal formats of the attached images violates an implementation-dependent set of restrictions.';
break;
}
throw new DeveloperError(message);
}
}
}
function applyRenderState(context, renderState, passState, clear) {
var previousRenderState = context._currentRenderState;
var previousPassState = context._currentPassState;
context._currentRenderState = renderState;
context._currentPassState = passState;
RenderState.partialApply(context._gl, previousRenderState, renderState, previousPassState, passState, clear);
}
var scratchBackBufferArray;
// this check must use typeof, not defined, because defined doesn't work with undeclared variables.
if (typeof WebGLRenderingContext !== 'undefined') {
scratchBackBufferArray = [WebGLConstants.BACK];
}
function bindFramebuffer(context, framebuffer) {
if (framebuffer !== context._currentFramebuffer) {
context._currentFramebuffer = framebuffer;
var buffers = scratchBackBufferArray;
if (defined(framebuffer)) {
framebuffer._bind();
validateFramebuffer(context);
// TODO: Need a way for a command to give what draw buffers are active.
buffers = framebuffer._getActiveColorAttachments();
} else {
var gl = context._gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
if (context.drawBuffers) {
context.glDrawBuffers(buffers);
}
}
}
var defaultClearCommand = new ClearCommand();
Context.prototype.clear = function(clearCommand, passState) {
clearCommand = defaultValue(clearCommand, defaultClearCommand);
passState = defaultValue(passState, this._defaultPassState);
var gl = this._gl;
var bitmask = 0;
var c = clearCommand.color;
var d = clearCommand.depth;
var s = clearCommand.stencil;
if (defined(c)) {
if (!Color.equals(this._clearColor, c)) {
Color.clone(c, this._clearColor);
gl.clearColor(c.red, c.green, c.blue, c.alpha);
}
bitmask |= gl.COLOR_BUFFER_BIT;
}
if (defined(d)) {
if (d !== this._clearDepth) {
this._clearDepth = d;
gl.clearDepth(d);
}
bitmask |= gl.DEPTH_BUFFER_BIT;
}
if (defined(s)) {
if (s !== this._clearStencil) {
this._clearStencil = s;
gl.clearStencil(s);
}
bitmask |= gl.STENCIL_BUFFER_BIT;
}
var rs = defaultValue(clearCommand.renderState, this._defaultRenderState);
applyRenderState(this, rs, passState, true);
// The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
var framebuffer = defaultValue(clearCommand.framebuffer, passState.framebuffer);
bindFramebuffer(this, framebuffer);
gl.clear(bitmask);
};
function beginDraw(context, framebuffer, drawCommand, passState) {
var rs = defaultValue(drawCommand._renderState, context._defaultRenderState);
if (defined(framebuffer) && rs.depthTest) {
if (rs.depthTest.enabled && !framebuffer.hasDepthAttachment) {
throw new DeveloperError('The depth test can not be enabled (drawCommand.renderState.depthTest.enabled) because the framebuffer (drawCommand.framebuffer) does not have a depth or depth-stencil renderbuffer.');
}
}
bindFramebuffer(context, framebuffer);
applyRenderState(context, rs, passState, false);
var sp = drawCommand._shaderProgram;
sp._bind();
context._maxFrameTextureUnitIndex = Math.max(context._maxFrameTextureUnitIndex, sp.maximumTextureUnitIndex);
}
function continueDraw(context, drawCommand) {
var primitiveType = drawCommand._primitiveType;
var va = drawCommand._vertexArray;
var offset = drawCommand._offset;
var count = drawCommand._count;
var instanceCount = drawCommand.instanceCount;
if (!PrimitiveType.validate(primitiveType)) {
throw new DeveloperError('drawCommand.primitiveType is required and must be valid.');
}
if (!defined(va)) {
throw new DeveloperError('drawCommand.vertexArray is required.');
}
if (offset < 0) {
throw new DeveloperError('drawCommand.offset must be greater than or equal to zero.');
}
if (count < 0) {
throw new DeveloperError('drawCommand.count must be greater than or equal to zero.');
}
if (instanceCount < 0) {
throw new DeveloperError('drawCommand.instanceCount must be greater than or equal to zero.');
}
if (instanceCount > 0 && !context.instancedArrays) {
throw new DeveloperError('Instanced arrays extension is not supported');
}
context._us.model = defaultValue(drawCommand._modelMatrix, Matrix4.IDENTITY);
drawCommand._shaderProgram._setUniforms(drawCommand._uniformMap, context._us, context.validateShaderProgram);
va._bind();
var indexBuffer = va.indexBuffer;
if (defined(indexBuffer)) {
offset = offset * indexBuffer.bytesPerIndex; // offset in vertices to offset in bytes
count = defaultValue(count, indexBuffer.numberOfIndices);
if (instanceCount === 0) {
context._gl.drawElements(primitiveType, count, indexBuffer.indexDatatype, offset);
} else {
context.glDrawElementsInstanced(primitiveType, count, indexBuffer.indexDatatype, offset, instanceCount);
}
} else {
count = defaultValue(count, va.numberOfVertices);
if (instanceCount === 0) {
context._gl.drawArrays(primitiveType, offset, count);
} else {
context.glDrawArraysInstanced(primitiveType, offset, count, instanceCount);
}
}
va._unBind();
}
Context.prototype.draw = function(drawCommand, passState) {
if (!defined(drawCommand)) {
throw new DeveloperError('drawCommand is required.');
}
if (!defined(drawCommand._shaderProgram)) {
throw new DeveloperError('drawCommand.shaderProgram is required.');
}
passState = defaultValue(passState, this._defaultPassState);
// The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
var framebuffer = defaultValue(drawCommand._framebuffer, passState.framebuffer);
beginDraw(this, framebuffer, drawCommand, passState);
continueDraw(this, drawCommand);
};
Context.prototype.endFrame = function() {
var gl = this._gl;
gl.useProgram(null);
this._currentFramebuffer = undefined;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
var buffers = scratchBackBufferArray;
if (this.drawBuffers) {
this.glDrawBuffers(buffers);
}
var length = this._maxFrameTextureUnitIndex;
this._maxFrameTextureUnitIndex = 0;
for (var i = 0; i < length; ++i) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
}
};
Context.prototype.readPixels = function(readState) {
var gl = this._gl;
readState = readState || {};
var x = Math.max(readState.x || 0, 0);
var y = Math.max(readState.y || 0, 0);
var width = readState.width || gl.drawingBufferWidth;
var height = readState.height || gl.drawingBufferHeight;
var framebuffer = readState.framebuffer;
if (width <= 0) {
throw new DeveloperError('readState.width must be greater than zero.');
}
if (height <= 0) {
throw new DeveloperError('readState.height must be greater than zero.');
}
var pixels = new Uint8Array(4 * width * height);
bindFramebuffer(this, framebuffer);
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
return pixels;
};
var viewportQuadAttributeLocations = {
position : 0,
textureCoordinates : 1
};
Context.prototype.getViewportQuadVertexArray = function() {
// Per-context cache for viewport quads
var vertexArray = this.cache.viewportQuad_vertexArray;
if (!defined(vertexArray)) {
var geometry = new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : [
-1.0, -1.0,
1.0, -1.0,
1.0, 1.0,
-1.0, 1.0
]
}),
textureCoordinates : new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 2,
values : [
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0
]
})
},
// Workaround Internet Explorer 11.0.8 lack of TRIANGLE_FAN
indices : new Uint16Array([0, 1, 2, 0, 2, 3]),
primitiveType : PrimitiveType.TRIANGLES
});
vertexArray = VertexArray.fromGeometry({
context : this,
geometry : geometry,
attributeLocations : viewportQuadAttributeLocations,
bufferUsage : BufferUsage.STATIC_DRAW,
interleave : true
});
this.cache.viewportQuad_vertexArray = vertexArray;
}
return vertexArray;
};
Context.prototype.createViewportQuadCommand = function(fragmentShaderSource, overrides) {
overrides = defaultValue(overrides, defaultValue.EMPTY_OBJECT);
return new DrawCommand({
vertexArray : this.getViewportQuadVertexArray(),
primitiveType : PrimitiveType.TRIANGLES,
renderState : overrides.renderState,
shaderProgram : ShaderProgram.fromCache({
context : this,
vertexShaderSource : ViewportQuadVS,
fragmentShaderSource : fragmentShaderSource,
attributeLocations : viewportQuadAttributeLocations
}),
uniformMap : overrides.uniformMap,
owner : overrides.owner,
framebuffer : overrides.framebuffer
});
};
Context.prototype.createPickFramebuffer = function() {
return new PickFramebuffer(this);
};
/**
* Gets the object associated with a pick color.
*
* @param {Color} pickColor The pick color.
* @returns {Object} The object associated with the pick color, or undefined if no object is associated with that color.
*
* @example
* var object = context.getObjectByPickColor(pickColor);
*
* @see Context#createPickId
*/
Context.prototype.getObjectByPickColor = function(pickColor) {
if (!defined(pickColor)) {
throw new DeveloperError('pickColor is required.');
}
return this._pickObjects[pickColor.toRgba()];
};
function PickId(pickObjects, key, color) {
this._pickObjects = pickObjects;
this.key = key;
this.color = color;
}
defineProperties(PickId.prototype, {
object : {
get : function() {
return this._pickObjects[this.key];
},
set : function(value) {
this._pickObjects[this.key] = value;
}
}
});
PickId.prototype.destroy = function() {
delete this._pickObjects[this.key];
return undefined;
};
/**
* Creates a unique ID associated with the input object for use with color-buffer picking.
* The ID has an RGBA color value unique to this context. You must call destroy()
* on the pick ID when destroying the input object.
*
* @param {Object} object The object to associate with the pick ID.
* @returns {Object} A PickId object with a color
property.
*
* @exception {RuntimeError} Out of unique Pick IDs.
*
*
* @example
* this._pickId = context.createPickId({
* primitive : this,
* id : this.id
* });
*
* @see Context#getObjectByPickColor
*/
Context.prototype.createPickId = function(object) {
if (!defined(object)) {
throw new DeveloperError('object is required.');
}
// the increment and assignment have to be separate statements to
// actually detect overflow in the Uint32 value
++this._nextPickColor[0];
var key = this._nextPickColor[0];
if (key === 0) {
// In case of overflow
throw new RuntimeError('Out of unique Pick IDs.');
}
this._pickObjects[key] = object;
return new PickId(this._pickObjects, key, Color.fromRgba(key));
};
Context.prototype.isDestroyed = function() {
return false;
};
Context.prototype.destroy = function() {
// Destroy all objects in the cache that have a destroy method.
var cache = this.cache;
for (var property in cache) {
if (cache.hasOwnProperty(property)) {
var propertyValue = cache[property];
if (defined(propertyValue.destroy)) {
propertyValue.destroy();
}
}
}
this._shaderCache = this._shaderCache.destroy();
this._defaultTexture = this._defaultTexture && this._defaultTexture.destroy();
this._defaultCubeMap = this._defaultCubeMap && this._defaultCubeMap.destroy();
return destroyObject(this);
};
return Context;
});
/*global define*/
define('Renderer/loadCubeMap',[
'../Core/defined',
'../Core/DeveloperError',
'../Core/loadImage',
'../ThirdParty/when',
'./CubeMap'
], function(
defined,
DeveloperError,
loadImage,
when,
CubeMap) {
'use strict';
/**
* Asynchronously loads six images and creates a cube map. Returns a promise that
* will resolve to a {@link CubeMap} once loaded, or reject if any image fails to load.
*
* @exports loadCubeMap
*
* @param {Context} context The context to use to create the cube map.
* @param {Object} urls The source of each image, or a promise for each URL. See the example below.
* @param {Boolean} [allowCrossOrigin=true] Whether to request the image using Cross-Origin
* Resource Sharing (CORS). CORS is only actually used if the image URL is actually cross-origin.
* Data URIs are never requested using CORS.
* @returns {Promise.} a promise that will resolve to the requested {@link CubeMap} when loaded.
*
* @exception {DeveloperError} context is required.
* @exception {DeveloperError} urls is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.
*
*
* @example
* Cesium.loadCubeMap(context, {
* positiveX : 'skybox_px.png',
* negativeX : 'skybox_nx.png',
* positiveY : 'skybox_py.png',
* negativeY : 'skybox_ny.png',
* positiveZ : 'skybox_pz.png',
* negativeZ : 'skybox_nz.png'
* }).then(function(cubeMap) {
* // use the cubemap
* }).otherwise(function(error) {
* // an error occurred
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*
* @private
*/
function loadCubeMap(context, urls, allowCrossOrigin) {
if (!defined(context)) {
throw new DeveloperError('context is required.');
}
if ((!defined(urls)) ||
(!defined(urls.positiveX)) ||
(!defined(urls.negativeX)) ||
(!defined(urls.positiveY)) ||
(!defined(urls.negativeY)) ||
(!defined(urls.positiveZ)) ||
(!defined(urls.negativeZ))) {
throw new DeveloperError('urls is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.');
}
// PERFORMANCE_IDEA: Given the size of some cube maps, we should consider tiling them, which
// would prevent hiccups when uploading, for example, six 4096x4096 textures to the GPU.
//
// Also, it is perhaps acceptable to use the context here in the callbacks, but
// ideally, we would do it in the primitive's update function.
var facePromises = [
loadImage(urls.positiveX, allowCrossOrigin),
loadImage(urls.negativeX, allowCrossOrigin),
loadImage(urls.positiveY, allowCrossOrigin),
loadImage(urls.negativeY, allowCrossOrigin),
loadImage(urls.positiveZ, allowCrossOrigin),
loadImage(urls.negativeZ, allowCrossOrigin)
];
return when.all(facePromises, function(images) {
return new CubeMap({
context : context,
source : {
positiveX : images[0],
negativeX : images[1],
positiveY : images[2],
negativeY : images[3],
positiveZ : images[4],
negativeZ : images[5]
}
});
});
}
return loadCubeMap;
});
/*global define*/
define('Scene/DiscardMissingTileImagePolicy',[
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/getImagePixels',
'../Core/loadImageViaBlob',
'../ThirdParty/when'
], function(
defaultValue,
defined,
DeveloperError,
getImagePixels,
loadImageViaBlob,
when) {
'use strict';
/**
* A policy for discarding tile images that match a known image containing a
* "missing" image.
*
* @alias DiscardMissingTileImagePolicy
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.missingImageUrl The URL of the known missing image.
* @param {Cartesian2[]} options.pixelsToCheck An array of {@link Cartesian2} pixel positions to
* compare against the missing image.
* @param {Boolean} [options.disableCheckIfAllPixelsAreTransparent=false] If true, the discard check will be disabled
* if all of the pixelsToCheck in the missingImageUrl have an alpha value of 0. If false, the
* discard check will proceed no matter the values of the pixelsToCheck.
*/
function DiscardMissingTileImagePolicy(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.missingImageUrl)) {
throw new DeveloperError('options.missingImageUrl is required.');
}
if (!defined(options.pixelsToCheck)) {
throw new DeveloperError('options.pixelsToCheck is required.');
}
this._pixelsToCheck = options.pixelsToCheck;
this._missingImagePixels = undefined;
this._missingImageByteLength = undefined;
this._isReady = false;
var that = this;
function success(image) {
if (defined(image.blob)) {
that._missingImageByteLength = image.blob.size;
}
var pixels = getImagePixels(image);
if (options.disableCheckIfAllPixelsAreTransparent) {
var allAreTransparent = true;
var width = image.width;
var pixelsToCheck = options.pixelsToCheck;
for (var i = 0, len = pixelsToCheck.length; allAreTransparent && i < len; ++i) {
var pos = pixelsToCheck[i];
var index = pos.x * 4 + pos.y * width;
var alpha = pixels[index + 3];
if (alpha > 0) {
allAreTransparent = false;
}
}
if (allAreTransparent) {
pixels = undefined;
}
}
that._missingImagePixels = pixels;
that._isReady = true;
}
function failure() {
// Failed to download "missing" image, so assume that any truly missing tiles
// will also fail to download and disable the discard check.
that._missingImagePixels = undefined;
that._isReady = true;
}
when(loadImageViaBlob(options.missingImageUrl), success, failure);
}
/**
* Determines if the discard policy is ready to process images.
* @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
*/
DiscardMissingTileImagePolicy.prototype.isReady = function() {
return this._isReady;
};
/**
* Given a tile image, decide whether to discard that image.
*
* @param {Image} image An image to test.
* @returns {Boolean} True if the image should be discarded; otherwise, false.
*
* @exception {DeveloperError} shouldDiscardImage
must not be called before the discard policy is ready.
*/
DiscardMissingTileImagePolicy.prototype.shouldDiscardImage = function(image) {
if (!this._isReady) {
throw new DeveloperError('shouldDiscardImage must not be called before the discard policy is ready.');
}
var pixelsToCheck = this._pixelsToCheck;
var missingImagePixels = this._missingImagePixels;
// If missingImagePixels is undefined, it indicates that the discard check has been disabled.
if (!defined(missingImagePixels)) {
return false;
}
if (defined(image.blob) && image.blob.size !== this._missingImageByteLength) {
return false;
}
var pixels = getImagePixels(image);
var width = image.width;
for (var i = 0, len = pixelsToCheck.length; i < len; ++i) {
var pos = pixelsToCheck[i];
var index = pos.x * 4 + pos.y * width;
for (var offset = 0; offset < 4; ++offset) {
var pixel = index + offset;
if (pixels[pixel] !== missingImagePixels[pixel]) {
return false;
}
}
}
return true;
};
return DiscardMissingTileImagePolicy;
});
/*global define*/
define('Scene/ImageryLayerFeatureInfo',[
'../Core/defined'
], function(
defined) {
'use strict';
/**
* Describes a rasterized feature, such as a point, polygon, polyline, etc., in an imagery layer.
*
* @alias ImageryLayerFeatureInfo
* @constructor
*/
function ImageryLayerFeatureInfo() {
/**
* Gets or sets the name of the feature.
* @type {String}
*/
this.name = undefined;
/**
* Gets or sets an HTML description of the feature. The HTML is not trusted and should
* be sanitized before display to the user.
* @type {String}
*/
this.description = undefined;
/**
* Gets or sets the position of the feature, or undefined if the position is not known.
*
* @type {Cartographic}
*/
this.position = undefined;
/**
* Gets or sets the raw data describing the feature. The raw data may be in any
* number of formats, such as GeoJSON, KML, etc.
* @type {Object}
*/
this.data = undefined;
/**
* Gets or sets the image layer of the feature.
* @type {Object}
*/
this.imageryLayer = undefined;
}
/**
* Configures the name of this feature by selecting an appropriate property. The name will be obtained from
* one of the following sources, in this order: 1) the property with the name 'name', 2) the property with the name 'title',
* 3) the first property containing the word 'name', 4) the first property containing the word 'title'. If
* the name cannot be obtained from any of these sources, the existing name will be left unchanged.
*
* @param {Object} properties An object literal containing the properties of the feature.
*/
ImageryLayerFeatureInfo.prototype.configureNameFromProperties = function(properties) {
var namePropertyPrecedence = 10;
var nameProperty;
for (var key in properties) {
if (properties.hasOwnProperty(key) && properties[key]) {
var lowerKey = key.toLowerCase();
if (namePropertyPrecedence > 1 && lowerKey === 'name') {
namePropertyPrecedence = 1;
nameProperty = key;
} else if (namePropertyPrecedence > 2 && lowerKey === 'title') {
namePropertyPrecedence = 2;
nameProperty = key;
} else if (namePropertyPrecedence > 3 && /name/i.test(key)) {
namePropertyPrecedence = 3;
nameProperty = key;
} else if (namePropertyPrecedence > 4 && /title/i.test(key)) {
namePropertyPrecedence = 4;
nameProperty = key;
}
}
}
if (defined(nameProperty)) {
this.name = properties[nameProperty];
}
};
/**
* Configures the description of this feature by creating an HTML table of properties and their values.
*
* @param {Object} properties An object literal containing the properties of the feature.
*/
ImageryLayerFeatureInfo.prototype.configureDescriptionFromProperties = function(properties) {
function describe(properties) {
var html = '';
for (var key in properties) {
if (properties.hasOwnProperty(key)) {
var value = properties[key];
if (defined(value)) {
if (typeof value === 'object') {
html += '' + key + ' ' + describe(value) + ' ';
} else {
html += '' + key + ' ' + value + ' ';
}
}
}
}
html += '
';
return html;
}
this.description = describe(properties);
};
return ImageryLayerFeatureInfo;
});
/*global define*/
define('Scene/ImageryProvider',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/loadImage',
'../Core/loadImageViaBlob',
'../Core/throttleRequestByServer'
], function(
defined,
defineProperties,
DeveloperError,
loadImage,
loadImageViaBlob,
throttleRequestByServer) {
'use strict';
/**
* Provides imagery to be displayed on the surface of an ellipsoid. This type describes an
* interface and is not intended to be instantiated directly.
*
* @alias ImageryProvider
* @constructor
*
* @see ArcGisMapServerImageryProvider
* @see SingleTileImageryProvider
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see MapboxImageryProvider
* @see createOpenStreetMapImageryProvider
* @see WebMapTileServiceImageryProvider
* @see WebMapServiceImageryProvider
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Layers.html|Cesium Sandcastle Imagery Layers Demo}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Layers%20Manipulation.html|Cesium Sandcastle Imagery Manipulation Demo}
*/
function ImageryProvider() {
/**
* The default alpha blending value of this provider, with 0.0 representing fully transparent and
* 1.0 representing fully opaque.
*
* @type {Number}
* @default undefined
*/
this.defaultAlpha = undefined;
/**
* The default brightness of this provider. 1.0 uses the unmodified imagery color. Less than 1.0
* makes the imagery darker while greater than 1.0 makes it brighter.
*
* @type {Number}
* @default undefined
*/
this.defaultBrightness = undefined;
/**
* The default contrast of this provider. 1.0 uses the unmodified imagery color. Less than 1.0 reduces
* the contrast while greater than 1.0 increases it.
*
* @type {Number}
* @default undefined
*/
this.defaultContrast = undefined;
/**
* The default hue of this provider in radians. 0.0 uses the unmodified imagery color.
*
* @type {Number}
* @default undefined
*/
this.defaultHue = undefined;
/**
* The default saturation of this provider. 1.0 uses the unmodified imagery color. Less than 1.0 reduces the
* saturation while greater than 1.0 increases it.
*
* @type {Number}
* @default undefined
*/
this.defaultSaturation = undefined;
/**
* The default gamma correction to apply to this provider. 1.0 uses the unmodified imagery color.
*
* @type {Number}
* @default undefined
*/
this.defaultGamma = undefined;
DeveloperError.throwInstantiationError();
}
defineProperties(ImageryProvider.prototype, {
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof ImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof ImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the rectangle, in radians, of the imagery provided by the instance. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle: {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link ImageryProvider#ready} returns true. Generally,
* a minimum level should only be used when the rectangle of the imagery is small
* enough that the number of tiles at the minimum level is small. An imagery
* provider with more than a few tiles at the minimum level will lead to
* rendering problems.
* @memberof ImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the tiling scheme used by the provider. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error.. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof ImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof ImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the proxy used by this provider.
* @memberof ImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof ImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
* @function
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
ImageryProvider.prototype.getTileCredits = DeveloperError.throwInstantiationError;
/**
* Requests the image for a given tile. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @function
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
ImageryProvider.prototype.requestImage = DeveloperError.throwInstantiationError;
/**
* Asynchronously determines what features, if any, are located at a given longitude and latitude within
* a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
* This function is optional, so it may not exist on all ImageryProviders.
*
* @function
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*
* @exception {DeveloperError} pickFeatures
must not be called before the imagery provider is ready.
*/
ImageryProvider.prototype.pickFeatures = DeveloperError.throwInstantiationError;
/**
* Loads an image from a given URL. If the server referenced by the URL already has
* too many requests pending, this function will instead return undefined, indicating
* that the request should be retried later.
*
* @param {String} url The URL of the image.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*/
ImageryProvider.loadImage = function(imageryProvider, url) {
if (defined(imageryProvider.tileDiscardPolicy)) {
return throttleRequestByServer(url, loadImageViaBlob);
}
return throttleRequestByServer(url, loadImage);
};
return ImageryProvider;
});
/*global define*/
define('Scene/ArcGisMapServerImageryProvider',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/GeographicTilingScheme',
'../Core/loadJson',
'../Core/loadJsonp',
'../Core/Math',
'../Core/Rectangle',
'../Core/RuntimeError',
'../Core/TileProviderError',
'../Core/WebMercatorProjection',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/when',
'./DiscardMissingTileImagePolicy',
'./ImageryLayerFeatureInfo',
'./ImageryProvider'
], function(
Cartesian2,
Cartesian3,
Cartographic,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
GeographicTilingScheme,
loadJson,
loadJsonp,
CesiumMath,
Rectangle,
RuntimeError,
TileProviderError,
WebMercatorProjection,
WebMercatorTilingScheme,
when,
DiscardMissingTileImagePolicy,
ImageryLayerFeatureInfo,
ImageryProvider) {
'use strict';
/**
* Provides tiled imagery hosted by an ArcGIS MapServer. By default, the server's pre-cached tiles are
* used, if available.
*
* @alias ArcGisMapServerImageryProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The URL of the ArcGIS MapServer service.
* @param {String} [options.token] The ArcGIS token used to authenticate with the ArcGIS MapServer service.
* @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
* is invalid and should be discarded. If this value is not specified, a default
* {@link DiscardMissingTileImagePolicy} is used for tiled map servers, and a
* {@link NeverTileDiscardPolicy} is used for non-tiled map servers. In the former case,
* we request tile 0,0 at the maximum tile level and check pixels (0,0), (200,20), (20,200),
* (80,110), and (160, 130). If all of these pixels are transparent, the discard check is
* disabled and no tiles are discarded. If any of them have a non-transparent color, any
* tile that has the same values in these pixel locations is discarded. The end result of
* these defaults should be correct tile discarding for a standard ArcGIS Server. To ensure
* that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this
* parameter.
* @param {Proxy} [options.proxy] A proxy to use for requests. This object is
* expected to have a getURL function which returns the proxied URL, if needed.
* @param {Boolean} [options.usePreCachedTilesIfAvailable=true] If true, the server's pre-cached
* tiles are used if they are available. If false, any pre-cached tiles are ignored and the
* 'export' service is used.
* @param {String} [options.layers] A comma-separated list of the layers to show, or undefined if all layers should be shown.
* @param {Boolean} [options.enablePickFeatures=true] If true, {@link ArcGisMapServerImageryProvider#pickFeatures} will invoke
* the Identify service on the MapServer and return the features included in the response. If false,
* {@link ArcGisMapServerImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
* without communicating with the server. Set this property to false if you don't want this provider's features to
* be pickable. Can be overridden by setting the {@link ArcGisMapServerImageryProvider#enablePickFeatures} property on the object.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer. This parameter is ignored when accessing
* a tiled layer.
* @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme to use to divide the world into tiles.
* This parameter is ignored when accessing a tiled server.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified and used,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
* parameter is specified, the WGS84 ellipsoid is used.
* @param {Number} [options.tileWidth=256] The width of each tile in pixels. This parameter is ignored when accessing a tiled server.
* @param {Number} [options.tileHeight=256] The height of each tile in pixels. This parameter is ignored when accessing a tiled server.
* @param {Number} [options.maximumLevel] The maximum tile level to request, or undefined if there is no maximum. This parameter is ignored when accessing
* a tiled server.
*
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see createOpenStreetMapImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
* @see UrlTemplateImageryProvider
*
*
* @example
* var esri = new Cesium.ArcGisMapServerImageryProvider({
* url : 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
* });
*
* @see {@link http://resources.esri.com/help/9.3/arcgisserver/apis/rest/|ArcGIS Server REST API}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
*/
function ArcGisMapServerImageryProvider(options) {
options = defaultValue(options, {});
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
this._url = options.url;
this._token = options.token;
this._tileDiscardPolicy = options.tileDiscardPolicy;
this._proxy = options.proxy;
this._tileWidth = defaultValue(options.tileWidth, 256);
this._tileHeight = defaultValue(options.tileHeight, 256);
this._maximumLevel = options.maximumLevel;
this._tilingScheme = defaultValue(options.tilingScheme, new GeographicTilingScheme({ ellipsoid : options.ellipsoid }));
this._credit = undefined;
this._useTiles = defaultValue(options.usePreCachedTilesIfAvailable, true);
this._rectangle = defaultValue(options.rectangle, this._tilingScheme.rectangle);
this._layers = options.layers;
/**
* Gets or sets a value indicating whether feature picking is enabled. If true, {@link ArcGisMapServerImageryProvider#pickFeatures} will
* invoke the "identify" operation on the ArcGIS server and return the features included in the response. If false,
* {@link ArcGisMapServerImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
* without communicating with the server.
* @type {Boolean}
* @default true
*/
this.enablePickFeatures = defaultValue(options.enablePickFeatures, true);
this._errorEvent = new Event();
this._ready = false;
this._readyPromise = when.defer();
// Grab the details of this MapServer.
var that = this;
var metadataError;
function metadataSuccess(data) {
var tileInfo = data.tileInfo;
if (!defined(tileInfo)) {
that._useTiles = false;
} else {
that._tileWidth = tileInfo.rows;
that._tileHeight = tileInfo.cols;
if (tileInfo.spatialReference.wkid === 102100 ||
tileInfo.spatialReference.wkid === 102113) {
that._tilingScheme = new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
} else if (data.tileInfo.spatialReference.wkid === 4326) {
that._tilingScheme = new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
} else {
var message = 'Tile spatial reference WKID ' + data.tileInfo.spatialReference.wkid + ' is not supported.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
return;
}
that._maximumLevel = data.tileInfo.lods.length - 1;
if (defined(data.fullExtent)) {
if (defined(data.fullExtent.spatialReference) && defined(data.fullExtent.spatialReference.wkid)) {
if (data.fullExtent.spatialReference.wkid === 102100 ||
data.fullExtent.spatialReference.wkid === 102113) {
var projection = new WebMercatorProjection();
var extent = data.fullExtent;
var sw = projection.unproject(new Cartesian3(Math.max(extent.xmin, -that._tilingScheme.ellipsoid.maximumRadius * Math.PI), Math.max(extent.ymin, -that._tilingScheme.ellipsoid.maximumRadius * Math.PI), 0.0));
var ne = projection.unproject(new Cartesian3(Math.min(extent.xmax, that._tilingScheme.ellipsoid.maximumRadius * Math.PI), Math.min(extent.ymax, that._tilingScheme.ellipsoid.maximumRadius * Math.PI), 0.0));
that._rectangle = new Rectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude);
} else if (data.fullExtent.spatialReference.wkid === 4326) {
that._rectangle = Rectangle.fromDegrees(data.fullExtent.xmin, data.fullExtent.ymin, data.fullExtent.xmax, data.fullExtent.ymax);
} else {
var extentMessage = 'fullExtent.spatialReference WKID ' + data.fullExtent.spatialReference.wkid + ' is not supported.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, extentMessage, undefined, undefined, undefined, requestMetadata);
return;
}
}
} else {
that._rectangle = that._tilingScheme.rectangle;
}
// Install the default tile discard policy if none has been supplied.
if (!defined(that._tileDiscardPolicy)) {
that._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
missingImageUrl : buildImageUrl(that, 0, 0, that._maximumLevel),
pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(200, 20), new Cartesian2(20, 200), new Cartesian2(80, 110), new Cartesian2(160, 130)],
disableCheckIfAllPixelsAreTransparent : true
});
}
that._useTiles = true;
}
if (defined(data.copyrightText) && data.copyrightText.length > 0) {
that._credit = new Credit(data.copyrightText);
}
that._ready = true;
that._readyPromise.resolve(true);
TileProviderError.handleSuccess(metadataError);
}
function metadataFailure(e) {
var message = 'An error occurred while accessing ' + that._url + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
that._readyPromise.reject(new RuntimeError(message));
}
function requestMetadata() {
var parameters = {
f: 'json'
};
if (defined(that._token)) {
parameters.token = that._token;
}
var metadata = loadJsonp(that._url, {
parameters : parameters,
proxy : that._proxy
});
when(metadata, metadataSuccess, metadataFailure);
}
if (this._useTiles) {
requestMetadata();
} else {
this._ready = true;
this._readyPromise.resolve(true);
}
}
function buildImageUrl(imageryProvider, x, y, level) {
var url;
if (imageryProvider._useTiles) {
url = imageryProvider._url + '/tile/' + level + '/' + y + '/' + x;
} else {
var nativeRectangle = imageryProvider._tilingScheme.tileXYToNativeRectangle(x, y, level);
var bbox = nativeRectangle.west + '%2C' + nativeRectangle.south + '%2C' + nativeRectangle.east + '%2C' + nativeRectangle.north;
url = imageryProvider._url + '/export?';
url += 'bbox=' + bbox;
if (imageryProvider._tilingScheme instanceof GeographicTilingScheme) {
url += '&bboxSR=4326&imageSR=4326';
} else {
url += '&bboxSR=3857&imageSR=3857';
}
url += '&size=' + imageryProvider._tileWidth + '%2C' + imageryProvider._tileHeight;
url += '&format=png&transparent=true&f=image';
if (imageryProvider.layers) {
url += '&layers=show:' + imageryProvider.layers;
}
}
var token = imageryProvider._token;
if (defined(token)) {
if (url.indexOf('?') === -1) {
url += '?';
}
if (url[url.length - 1] !== '?'){
url += '&';
}
url += 'token=' + token;
}
var proxy = imageryProvider._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
return url;
}
defineProperties(ArcGisMapServerImageryProvider.prototype, {
/**
* Gets the URL of the ArcGIS MapServer.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the ArcGIS token used to authenticate with the ArcGis MapServer service.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {String}
* @readonly
*/
token : {
get : function() {
return this._token;
}
},
/**
* Gets the proxy used by this provider.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._proxy;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
}
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight: {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
}
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
}
return this._maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
}
return 0;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
}
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
if (!this._ready) {
throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
}
return this._rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
}
return this._tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
* @memberof ArcGisMapServerImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets a value indicating whether this imagery provider is using pre-cached tiles from the
* ArcGIS MapServer. If the imagery provider is not yet ready ({@link ArcGisMapServerImageryProvider#ready}), this function
* will return the value of `options.usePreCachedTilesIfAvailable`, even if the MapServer does
* not have pre-cached tiles.
* @memberof ArcGisMapServerImageryProvider.prototype
*
* @type {Boolean}
* @readonly
* @default true
*/
usingPrecachedTiles : {
get : function() {
return this._useTiles;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof ArcGisMapServerImageryProvider.prototype
*
* @type {Boolean}
* @readonly
* @default true
*/
hasAlphaChannel : {
get : function() {
return true;
}
},
/**
* Gets the comma-separated list of layer IDs to show.
* @memberof ArcGisMapServerImageryProvider.prototype
*
* @type {String}
*/
layers : {
get : function() {
return this._layers;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
ArcGisMapServerImageryProvider.prototype.getTileCredits = function(x, y, level) {
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
ArcGisMapServerImageryProvider.prototype.requestImage = function(x, y, level) {
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
}
var url = buildImageUrl(this, x, y, level);
return ImageryProvider.loadImage(this, url);
};
/**
/**
* Asynchronously determines what features, if any, are located at a given longitude and latitude within
* a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
*
* @exception {DeveloperError} pickFeatures
must not be called before the imagery provider is ready.
*/
ArcGisMapServerImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
if (!this._ready) {
throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.');
}
if (!this.enablePickFeatures) {
return undefined;
}
var rectangle = this._tilingScheme.tileXYToNativeRectangle(x, y, level);
var horizontal;
var vertical;
var sr;
if (this._tilingScheme instanceof GeographicTilingScheme) {
horizontal = CesiumMath.toDegrees(longitude);
vertical = CesiumMath.toDegrees(latitude);
sr = '4326';
} else {
var projected = this._tilingScheme.projection.project(new Cartographic(longitude, latitude, 0.0));
horizontal = projected.x;
vertical = projected.y;
sr = '3857';
}
var url = this._url + '/identify?f=json&tolerance=2&geometryType=esriGeometryPoint';
url += '&geometry=' + horizontal + ',' + vertical;
url += '&mapExtent=' + rectangle.west + ',' + rectangle.south + ',' + rectangle.east + ',' + rectangle.north;
url += '&imageDisplay=' + this._tileWidth + ',' + this._tileHeight + ',96';
url += '&sr=' + sr;
url += '&layers=visible';
if (defined(this._layers)) {
url += ':' + this._layers;
}
if (defined(this._token)) {
url += '&token=' + this._token;
}
if (defined(this._proxy)) {
url = this._proxy.getURL(url);
}
return loadJson(url).then(function(json) {
var result = [];
var features = json.results;
if (!defined(features)) {
return result;
}
for (var i = 0; i < features.length; ++i) {
var feature = features[i];
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.data = feature;
featureInfo.name = feature.value;
featureInfo.properties = feature.attributes;
featureInfo.configureDescriptionFromProperties(feature.attributes);
// If this is a point feature, use the coordinates of the point.
if (feature.geometryType === 'esriGeometryPoint' && feature.geometry) {
var wkid = feature.geometry.spatialReference && feature.geometry.spatialReference.wkid ? feature.geometry.spatialReference.wkid : 4326;
if (wkid === 4326 || wkid === 4283) {
featureInfo.position = Cartographic.fromDegrees(feature.geometry.x, feature.geometry.y, feature.geometry.z);
} else if (wkid === 102100 || wkid === 900913 || wkid === 3857) {
var projection = new WebMercatorProjection();
featureInfo.position = projection.unproject(new Cartesian3(feature.geometry.x, feature.geometry.y, feature.geometry.z));
}
}
result.push(featureInfo);
}
return result;
});
};
return ArcGisMapServerImageryProvider;
});
/*global define*/
define('Scene/BingMapsStyle',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* The types of imagery provided by Bing Maps.
*
* @exports BingMapsStyle
*
* @see BingMapsImageryProvider
*/
var BingMapsStyle = {
/**
* Aerial imagery.
*
* @type {String}
* @constant
*/
AERIAL : 'Aerial',
/**
* Aerial imagery with a road overlay.
*
* @type {String}
* @constant
*/
AERIAL_WITH_LABELS : 'AerialWithLabels',
/**
* Roads without additional imagery.
*
* @type {String}
* @constant
*/
ROAD : 'Road',
/**
* Ordnance Survey imagery
*
* @type {String}
* @constant
*/
ORDNANCE_SURVEY : 'OrdnanceSurvey',
/**
* Collins Bart imagery.
*
* @type {String}
* @constant
*/
COLLINS_BART : 'CollinsBart'
};
return freezeObject(BingMapsStyle);
});
/*global define*/
define('Scene/BingMapsImageryProvider',[
'../Core/BingMapsApi',
'../Core/Cartesian2',
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/loadJsonp',
'../Core/Math',
'../Core/Rectangle',
'../Core/RuntimeError',
'../Core/TileProviderError',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/when',
'./BingMapsStyle',
'./DiscardMissingTileImagePolicy',
'./ImageryProvider'
], function(
BingMapsApi,
Cartesian2,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
loadJsonp,
CesiumMath,
Rectangle,
RuntimeError,
TileProviderError,
WebMercatorTilingScheme,
when,
BingMapsStyle,
DiscardMissingTileImagePolicy,
ImageryProvider) {
'use strict';
/**
* Provides tiled imagery using the Bing Maps Imagery REST API.
*
* @alias BingMapsImageryProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The url of the Bing Maps server hosting the imagery.
* @param {String} [options.key] The Bing Maps key for your application, which can be
* created at {@link https://www.bingmapsportal.com/}.
* If this parameter is not provided, {@link BingMapsApi.defaultKey} is used.
* If {@link BingMapsApi.defaultKey} is undefined as well, a message is
* written to the console reminding you that you must create and supply a Bing Maps
* key as soon as possible. Please do not deploy an application that uses
* Bing Maps imagery without creating a separate key for your application.
* @param {String} [options.tileProtocol] The protocol to use when loading tiles, e.g. 'http:' or 'https:'.
* By default, tiles are loaded using the same protocol as the page.
* @param {String} [options.mapStyle=BingMapsStyle.AERIAL] The type of Bing Maps
* imagery to load.
* @param {String} [options.culture=''] The culture to use when requesting Bing Maps imagery. Not
* all cultures are supported. See {@link http://msdn.microsoft.com/en-us/library/hh441729.aspx}
* for information on the supported cultures.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
* is invalid and should be discarded. If this value is not specified, a default
* {@link DiscardMissingTileImagePolicy} is used which requests
* tile 0,0 at the maximum tile level and checks pixels (0,0), (120,140), (130,160),
* (200,50), and (200,200). If all of these pixels are transparent, the discard check is
* disabled and no tiles are discarded. If any of them have a non-transparent color, any
* tile that has the same values in these pixel locations is discarded. The end result of
* these defaults should be correct tile discarding for a standard Bing Maps server. To ensure
* that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this
* parameter.
* @param {Proxy} [options.proxy] A proxy to use for requests. This object is
* expected to have a getURL function which returns the proxied URL, if needed.
*
* @see ArcGisMapServerImageryProvider
* @see GoogleEarthImageryProvider
* @see createOpenStreetMapImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
* @see UrlTemplateImageryProvider
*
*
* @example
* var bing = new Cesium.BingMapsImageryProvider({
* url : 'https://dev.virtualearth.net',
* key : 'get-yours-at-https://www.bingmapsportal.com/',
* mapStyle : Cesium.BingMapsStyle.AERIAL
* });
*
* @see {@link http://msdn.microsoft.com/en-us/library/ff701713.aspx|Bing Maps REST Services}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
*/
function BingMapsImageryProvider(options) {
options = defaultValue(options, {});
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
this._key = BingMapsApi.getKey(options.key);
this._keyErrorCredit = BingMapsApi.getErrorCredit(options.key);
this._url = options.url;
this._tileProtocol = options.tileProtocol;
this._mapStyle = defaultValue(options.mapStyle, BingMapsStyle.AERIAL);
this._culture = defaultValue(options.culture, '');
this._tileDiscardPolicy = options.tileDiscardPolicy;
this._proxy = options.proxy;
this._credit = new Credit('Bing Imagery', BingMapsImageryProvider._logoData, 'http://www.bing.com');
/**
* The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider.
* Changing this value after creating an {@link ImageryLayer} for this provider will have
* no effect. Instead, set the layer's {@link ImageryLayer#gamma} property.
*
* @type {Number}
* @default 1.0
*/
this.defaultGamma = 1.0;
this._tilingScheme = new WebMercatorTilingScheme({
numberOfLevelZeroTilesX : 2,
numberOfLevelZeroTilesY : 2,
ellipsoid : options.ellipsoid
});
this._tileWidth = undefined;
this._tileHeight = undefined;
this._maximumLevel = undefined;
this._imageUrlTemplate = undefined;
this._imageUrlSubdomains = undefined;
this._errorEvent = new Event();
this._ready = false;
this._readyPromise = when.defer();
var metadataUrl = this._url + '/REST/v1/Imagery/Metadata/' + this._mapStyle + '?incl=ImageryProviders&key=' + this._key;
var that = this;
var metadataError;
function metadataSuccess(data) {
var resource = data.resourceSets[0].resources[0];
that._tileWidth = resource.imageWidth;
that._tileHeight = resource.imageHeight;
that._maximumLevel = resource.zoomMax - 1;
that._imageUrlSubdomains = resource.imageUrlSubdomains;
that._imageUrlTemplate = resource.imageUrl.replace('{culture}', that._culture);
var tileProtocol = that._tileProtocol;
if (!defined(tileProtocol)) {
// use the document's protocol, unless it's not http or https
var documentProtocol = document.location.protocol;
tileProtocol = /^http/.test(documentProtocol) ? documentProtocol : 'http:';
}
that._imageUrlTemplate = that._imageUrlTemplate.replace(/^http:/, tileProtocol);
// Install the default tile discard policy if none has been supplied.
if (!defined(that._tileDiscardPolicy)) {
that._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
missingImageUrl : buildImageUrl(that, 0, 0, that._maximumLevel),
pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)],
disableCheckIfAllPixelsAreTransparent : true
});
}
var attributionList = that._attributionList = resource.imageryProviders;
if (!attributionList) {
attributionList = that._attributionList = [];
}
for (var attributionIndex = 0, attributionLength = attributionList.length; attributionIndex < attributionLength; ++attributionIndex) {
var attribution = attributionList[attributionIndex];
attribution.credit = new Credit(attribution.attribution);
var coverageAreas = attribution.coverageAreas;
for (var areaIndex = 0, areaLength = attribution.coverageAreas.length; areaIndex < areaLength; ++areaIndex) {
var area = coverageAreas[areaIndex];
var bbox = area.bbox;
area.bbox = new Rectangle(
CesiumMath.toRadians(bbox[1]),
CesiumMath.toRadians(bbox[0]),
CesiumMath.toRadians(bbox[3]),
CesiumMath.toRadians(bbox[2]));
}
}
that._ready = true;
that._readyPromise.resolve(true);
TileProviderError.handleSuccess(metadataError);
}
function metadataFailure(e) {
var message = 'An error occurred while accessing ' + metadataUrl + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
that._readyPromise.reject(new RuntimeError(message));
}
function requestMetadata() {
var metadata = loadJsonp(metadataUrl, {
callbackParameterName : 'jsonp',
proxy : that._proxy
});
when(metadata, metadataSuccess, metadataFailure);
}
requestMetadata();
}
defineProperties(BingMapsImageryProvider.prototype, {
/**
* Gets the name of the BingMaps server url hosting the imagery.
* @memberof BingMapsImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the proxy used by this provider.
* @memberof BingMapsImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._proxy;
}
},
/**
* Gets the Bing Maps key.
* @memberof BingMapsImageryProvider.prototype
* @type {String}
* @readonly
*/
key : {
get : function() {
return this._key;
}
},
/**
* Gets the type of Bing Maps imagery to load.
* @memberof BingMapsImageryProvider.prototype
* @type {BingMapsStyle}
* @readonly
*/
mapStyle : {
get : function() {
return this._mapStyle;
}
},
/**
* The culture to use when requesting Bing Maps imagery. Not
* all cultures are supported. See {@link http://msdn.microsoft.com/en-us/library/hh441729.aspx}
* for information on the supported cultures.
* @memberof BingMapsImageryProvider.prototype
* @type {String}
* @readonly
*/
culture : {
get : function() {
return this._culture;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
}
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight: {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
}
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
}
return this._maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
}
return 0;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
}
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
if (!this._ready) {
throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
}
return this._tilingScheme.rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
}
return this._tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof BingMapsImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof BingMapsImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof BingMapsImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link BingMapsImageryProvider#ready} returns true.
* @memberof BingMapsImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage
* and texture upload time.
* @memberof BingMapsImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return false;
}
}
});
var rectangleScratch = new Rectangle();
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
BingMapsImageryProvider.prototype.getTileCredits = function(x, y, level) {
if (!this._ready) {
throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.');
}
var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch);
var result = getRectangleAttribution(this._attributionList, level, rectangle);
if (defined(this._keyErrorCredit)) {
result.push(this._keyErrorCredit);
}
return result;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link BingMapsImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
BingMapsImageryProvider.prototype.requestImage = function(x, y, level) {
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
}
var url = buildImageUrl(this, x, y, level);
return ImageryProvider.loadImage(this, url);
};
/**
* Picking features is not currently supported by this imagery provider, so this function simply returns
* undefined.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
BingMapsImageryProvider.prototype.pickFeatures = function() {
return undefined;
};
BingMapsImageryProvider._logoData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD0AAAAaCAYAAAAEy1RnAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3gIDEgcPTMnXOQAAClZJREFUWMPdWGtsFNcV/u689uH1+sXaONhlWQzBENtxiUFBpBSLd60IpXHSNig4URtSYQUkRJNSi0igViVVVBJBaBsiAgKRQJSG8AgEHCCWU4iBCprY2MSgXfOI16y9D3s9Mzsztz9yB12WNU2i9Ecy0tHOzN4793zn3POdcy7BnRfJ8I7iB3SRDPeEExswLz8Y0DZIAYDIRGAgLQAm+7Xle31J3L3Anp1MZPY+BUBjorN332vgYhpgV1FRUd6TTz45ubq6OtDV1SXpuu5g//Oept9wNwlMyAi8IXDjyF245TsDTdivDMATCATGNDU1/WbhwoWPTZs2bWx1dXWhx+Oxrl+/PqTrus5t9W8KWEzjinTAYhro/xuBStwiIgBnJBLxKIoy1u/3V/r9/krDMMz3339/Z3t7e38ikUgCMDLEt8W+Q0cAI3McYTDDmZxh7DESG5Ni43jg9Gsa+X+OsxWxPSJTSj3JZFK5ZRVJErOzs8e6XC4fgGwALhbzDgAKU1hK28KEA6PMmTMn56233qpevnz5PQDcbJ7EzVUAuMrLy3MBeABkcWOEDELSyFe4y7iMoHkriZZlKYZh8ASHZDKpJJPJHAC5APIA5APIAeBlCjo5TwlpXnbOmTPHP3fu3KZVq1atZKBcDJQ9x7V48WJfc3Pzhp6enj+tXLnyR8w4MjdG4gyVDk7KICMClzKlLUrpbQMNw5AkScppbGz8cWdn57WjR4/2caw+DEBlYjO8wX1foZQWuN3uKZIklQD4G+fhlG0Yl8uVm5WVVW6app6dne0D0G8vnxbjJntHubCUOK/badZICyWanrJuAaeUknTQpmlKkUhEWbx48U8LCwtHhUKha+fPn+85fPhwV0tLyzUACSZx9jvMFhIByNFoVDEMw/qKB5HPvJfkUqBr9+7deklJyZ/j8bi5ffv2OAslieMLsG+m2DybT2QuzEQOsF5SUqJfvXo1yc2l6Xn6rgSRSCSEc+fOhVeuXLmwoqJixvTp0wcWLFgQ7unpudHR0dF97ty5z/fu3XseQJh5adjeerquy5ZlCalUivh8Pt8HH3ywzOPxyD09PZ81NjZ+2NnZaQEQx40b54vFYqaqquEVK1b4a2tr/WvWrDn18ssv144fP36SqqoD69ev371nz57rDLwAwHHkyJGfjRs3rtowDOv06dOnu7q6rs6bN2/s7Nmz9zIjDKenWoFZKg/AlMLCwl82Nzf/m3LX22+/fXb06NF/ALC8u7u7m6ZdkUhksL29/UpLS0vzunXrVgAoBzAaQBGAiY2NjUui0ei1RCLRFwwG/9PX19cVi8WCqqoOdHd3HysrK6sDMCccDl8IBoOtiqIsOnbs2D+i0eiV3t7ez8Ph8GeRSKRT07TB/v7+i1OnTp0HYBqABzs7O/+paVo0Fot1RyKRi/F4/Gp/f39XIpHoZnoUMn6wU+ZtRDaymwmxZFk2AWjvvvvuJ/F4PMn/n5+fn1VeXu6fOXNmbU1NzUOM4Bz8QqIoyg6HwxuLxfq3bdu2a+vWrW/09/dfKy0tffDVV199BEC20+n0ud3uQgBup9Pp83g8JYqieE+ePPnxxo0bt33xxRen8/Ly7n3hhRcWASh47bXX5pWVldWFw+GuXbt27XjzzTd3BoPBDq/XG1AUZRRHmAKPVfqaoKkgCCkA+oYNG84Eg0FHTU1N5ezZs8eWlJQ4CSF8/LvZYhJPQoQQpFKpwcrKyo1su9HBwUF99erVv588eXINgOOmacIwDEopdaZSKUIpxYkTJz6sr68/BMBav379RcMwZk2aNOl+AP+qq6t7xDTNVEVFxR+j0WgSAJk4ceKlTz/9tNzpdHpZvIvpjVW6pykhhBJCbkvwgiAQQogEQL558ybdtGlTsLm5OWJZdxZmlmWll5OUEEJN0zSGhob6GcOrALSzZ8/2apqWcLlc2axGACNRkRAimqaph0Kh68xIwwB0y7IMSZKcABz5+fkl8Xj8y2g0apOb5na7rYGBgS/JV54Q0qpAAoBKaS0jBWClg1ZVFeFw2AlgVF1dXeDpp5+eWVFRUVpcXOzgvQwAbrcbDJhdudlGpKZpGtx6JCcnRxIEQbQsS2PjbjM+AMvlchnMSBaXkr7ymCCIhmEYfMoVRVESBEHI0CaTTNubssUsQRBuubCtra33pZdeCk6YMCGwZs2aipqaGn9paWmuJEl3JP0bN258eeTIkRMABrm0YomiaImiKGVlZeWxLecAgBkzZvgdDkfWjRs3ggA0bpfpoiiahBCqKEqKAy2yULMA6MlkMp6Xl3cP1x2SWCwmFhQU+CmlFhfHNFOevpX4LcvSJUkyAeDQoUOh119//fpTTz01Zf78+UWBQCBHUZQ7yE/TNGPfvn0n33vvvSP79+//BECMeZsCMGRZNgRBgNPpHHXx4sVVDQ0Nf1+wYMGYJ554YikAevDgwUMA4oIgQJZlSggZdDqdBiGEZGdn6ww0tQlJURTT4/EMHz9+/MCjjz7622AwuHbZsmVbiouLvWvXrm1wOp3ZqVRqaKQTIInf1gAMl8ulU0q1CxcuBGOxmL5u3bryQCDgycrKEjORXGtra8eOHTsOHz169OyVK1cuA+hlRYrGlNRkWR7UNO2mYRiaz+cb3dLS8gYhhOi6Hj116tSOVatWHQNALcsaME0zLghClBDSZ9+zQsZ2SoJS2udwOKLPPffcvsrKyrJAIPDQ/v37txiGofX19V3r7e29UlBQMHqEVpjwnrYA6PF4PK6q6s2qqqqpZWVlitvtljOB7enpiWzbtu3wgQMHTre1tV0E0MeKkkGuIhMAqHv37u30er3Px+NxlyiKygMPPOAnhFiXLl0Kbd68uYPNsXbu3Lk6mUwaqqr2btmyZUdtbe3hd955pwvAEFNcO3jw4K/b2tqiqqpGIpGI4/HHH/9rQ0PDCa/XOyoSidDLly8PNTU1PcZ4QuNK1ju6NYHFRAGASXPnzv1Fa2vrxzTDpapqateuXR/Nnz+/SVGUhwFMBzCBFSLZLF75DsrJGpXRAH4EIABgPIBxAEoBFAPwARjFif1sNzZ25+VlOhaxufcCqAFQC+BhAPVLliz5XSqVUkOhUAuAKWnFyR3dlsw+fg+A+8eMGfPzTZs2bY9GozEb8JkzZ9qXLl36l+Li4l8B+AmAyQDGsGrOzfXNPGPawG2l85jksmcPm+vihH+2W1iF3bvZPN+sWbPuGx4eDrW3t+85fvz41o6OjmZN04Y0TYvV19cvYIbN5QqUjG2mwj5YAqDK4XDMe+aZZ55vbW09+sorr2yuqqpqYFatAuBn3uB7XzJCY297XeaUd2RoGzOJmHb6IjFj5D777LP3DQwMfDw8PBxSVbUvkUj0hEKhj1588cXH2O7zMSPdplumoxveMx5Zlj3jx4/39vb26gMDA4MsvgYZo+p8Pr7LqQX5Ds/U7d0jFxUVZS1atKg4Nzc317Isp67rZldXV6y5ufkmI78hFtcmrx8ZweMit6XsUs4+6kmlgbW+peLf9gyMZNCR374G0y/FxEzX8b/8+bkXEBxKFwAAAABJRU5ErkJggg==';
/**
* Converts a tiles (x, y, level) position into a quadkey used to request an image
* from a Bing Maps server.
*
* @param {Number} x The tile's x coordinate.
* @param {Number} y The tile's y coordinate.
* @param {Number} level The tile's zoom level.
*
* @see {@link http://msdn.microsoft.com/en-us/library/bb259689.aspx|Bing Maps Tile System}
* @see BingMapsImageryProvider#quadKeyToTileXY
*/
BingMapsImageryProvider.tileXYToQuadKey = function(x, y, level) {
var quadkey = '';
for ( var i = level; i >= 0; --i) {
var bitmask = 1 << i;
var digit = 0;
if ((x & bitmask) !== 0) {
digit |= 1;
}
if ((y & bitmask) !== 0) {
digit |= 2;
}
quadkey += digit;
}
return quadkey;
};
/**
* Converts a tile's quadkey used to request an image from a Bing Maps server into the
* (x, y, level) position.
*
* @param {String} quadkey The tile's quad key
*
* @see {@link http://msdn.microsoft.com/en-us/library/bb259689.aspx|Bing Maps Tile System}
* @see BingMapsImageryProvider#tileXYToQuadKey
*/
BingMapsImageryProvider.quadKeyToTileXY = function(quadkey) {
var x = 0;
var y = 0;
var level = quadkey.length - 1;
for ( var i = level; i >= 0; --i) {
var bitmask = 1 << i;
var digit = +quadkey[level - i];
if ((digit & 1) !== 0) {
x |= bitmask;
}
if ((digit & 2) !== 0) {
y |= bitmask;
}
}
return {
x : x,
y : y,
level : level
};
};
function buildImageUrl(imageryProvider, x, y, level) {
var imageUrl = imageryProvider._imageUrlTemplate;
var quadkey = BingMapsImageryProvider.tileXYToQuadKey(x, y, level);
imageUrl = imageUrl.replace('{quadkey}', quadkey);
var subdomains = imageryProvider._imageUrlSubdomains;
var subdomainIndex = (x + y + level) % subdomains.length;
imageUrl = imageUrl.replace('{subdomain}', subdomains[subdomainIndex]);
var proxy = imageryProvider._proxy;
if (defined(proxy)) {
imageUrl = proxy.getURL(imageUrl);
}
return imageUrl;
}
var intersectionScratch = new Rectangle();
function getRectangleAttribution(attributionList, level, rectangle) {
// Bing levels start at 1, while ours start at 0.
++level;
var result = [];
for (var attributionIndex = 0, attributionLength = attributionList.length; attributionIndex < attributionLength; ++attributionIndex) {
var attribution = attributionList[attributionIndex];
var coverageAreas = attribution.coverageAreas;
var included = false;
for (var areaIndex = 0, areaLength = attribution.coverageAreas.length; !included && areaIndex < areaLength; ++areaIndex) {
var area = coverageAreas[areaIndex];
if (level >= area.zoomMin && level <= area.zoomMax) {
var intersection = Rectangle.intersection(rectangle, area.bbox, intersectionScratch);
if (defined(intersection)) {
included = true;
}
}
}
if (included) {
result.push(attribution.credit);
}
}
return result;
}
return BingMapsImageryProvider;
});
/*global define*/
define('Scene/CullingVolume',[
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/Intersect',
'../Core/Plane'
], function(
Cartesian3,
Cartesian4,
defaultValue,
defined,
DeveloperError,
Intersect,
Plane) {
'use strict';
/**
* The culling volume defined by planes.
*
* @alias CullingVolume
* @constructor
*
* @param {Cartesian4[]} [planes] An array of clipping planes.
*/
function CullingVolume(planes) {
/**
* Each plane is represented by a Cartesian4 object, where the x, y, and z components
* define the unit vector normal to the plane, and the w component is the distance of the
* plane from the origin.
* @type {Cartesian4[]}
* @default []
*/
this.planes = defaultValue(planes, []);
}
var faces = [new Cartesian3(), new Cartesian3(), new Cartesian3()];
Cartesian3.clone(Cartesian3.UNIT_X, faces[0]);
Cartesian3.clone(Cartesian3.UNIT_Y, faces[1]);
Cartesian3.clone(Cartesian3.UNIT_Z, faces[2]);
var scratchPlaneCenter = new Cartesian3();
var scratchPlaneNormal = new Cartesian3();
var scratchPlane = new Plane(new Cartesian3(), 0.0);
/**
* Constructs a culling volume from a bounding sphere. Creates six planes that create a box containing the sphere.
* The planes are aligned to the x, y, and z axes in world coordinates.
*
* @param {BoundingSphere} boundingSphere The bounding sphere used to create the culling volume.
* @param {CullingVolume} [result] The object onto which to store the result.
* @returns {CullingVolume} The culling volume created from the bounding sphere.
*/
CullingVolume.fromBoundingSphere = function(boundingSphere, result) {
if (!defined(boundingSphere)) {
throw new DeveloperError('boundingSphere is required.');
}
if (!defined(result)) {
result = new CullingVolume();
}
var length = faces.length;
var planes = result.planes;
planes.length = 2 * length;
var center = boundingSphere.center;
var radius = boundingSphere.radius;
var planeIndex = 0;
for (var i = 0; i < length; ++i) {
var faceNormal = faces[i];
var plane0 = planes[planeIndex];
var plane1 = planes[planeIndex + 1];
if (!defined(plane0)) {
plane0 = planes[planeIndex] = new Cartesian4();
}
if (!defined(plane1)) {
plane1 = planes[planeIndex + 1] = new Cartesian4();
}
Cartesian3.multiplyByScalar(faceNormal, -radius, scratchPlaneCenter);
Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter);
plane0.x = faceNormal.x;
plane0.y = faceNormal.y;
plane0.z = faceNormal.z;
plane0.w = -Cartesian3.dot(faceNormal, scratchPlaneCenter);
Cartesian3.multiplyByScalar(faceNormal, radius, scratchPlaneCenter);
Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter);
plane1.x = -faceNormal.x;
plane1.y = -faceNormal.y;
plane1.z = -faceNormal.z;
plane1.w = -Cartesian3.dot(Cartesian3.negate(faceNormal, scratchPlaneNormal), scratchPlaneCenter);
planeIndex += 2;
}
return result;
};
/**
* Determines whether a bounding volume intersects the culling volume.
*
* @param {Object} boundingVolume The bounding volume whose intersection with the culling volume is to be tested.
* @returns {Intersect} Intersect.OUTSIDE, Intersect.INTERSECTING, or Intersect.INSIDE.
*/
CullingVolume.prototype.computeVisibility = function(boundingVolume) {
if (!defined(boundingVolume)) {
throw new DeveloperError('boundingVolume is required.');
}
var planes = this.planes;
var intersecting = false;
for (var k = 0, len = planes.length; k < len; ++k) {
var result = boundingVolume.intersectPlane(Plane.fromCartesian4(planes[k], scratchPlane));
if (result === Intersect.OUTSIDE) {
return Intersect.OUTSIDE;
} else if (result === Intersect.INTERSECTING) {
intersecting = true;
}
}
return intersecting ? Intersect.INTERSECTING : Intersect.INSIDE;
};
/**
* Determines whether a bounding volume intersects the culling volume.
*
* @param {Object} boundingVolume The bounding volume whose intersection with the culling volume is to be tested.
* @param {Number} parentPlaneMask A bit mask from the boundingVolume's parent's check against the same culling
* volume, such that if (planeMask & (1 << planeIndex) === 0), for k < 31, then
* the parent (and therefore this) volume is completely inside plane[planeIndex]
* and that plane check can be skipped.
* @returns {Number} A plane mask as described above (which can be applied to this boundingVolume's children).
*
* @private
*/
CullingVolume.prototype.computeVisibilityWithPlaneMask = function(boundingVolume, parentPlaneMask) {
if (!defined(boundingVolume)) {
throw new DeveloperError('boundingVolume is required.');
}
if (!defined(parentPlaneMask)) {
throw new DeveloperError('parentPlaneMask is required.');
}
if (parentPlaneMask === CullingVolume.MASK_OUTSIDE || parentPlaneMask === CullingVolume.MASK_INSIDE) {
// parent is completely outside or completely inside, so this child is as well.
return parentPlaneMask;
}
// Start with MASK_INSIDE (all zeros) so that after the loop, the return value can be compared with MASK_INSIDE.
// (Because if there are fewer than 31 planes, the upper bits wont be changed.)
var mask = CullingVolume.MASK_INSIDE;
var planes = this.planes;
for (var k = 0, len = planes.length; k < len; ++k) {
// For k greater than 31 (since 31 is the maximum number of INSIDE/INTERSECTING bits we can store), skip the optimization.
var flag = (k < 31) ? (1 << k) : 0;
if (k < 31 && (parentPlaneMask & flag) === 0) {
// boundingVolume is known to be INSIDE this plane.
continue;
}
var result = boundingVolume.intersectPlane(Plane.fromCartesian4(planes[k], scratchPlane));
if (result === Intersect.OUTSIDE) {
return CullingVolume.MASK_OUTSIDE;
} else if (result === Intersect.INTERSECTING) {
mask |= flag;
}
}
return mask;
};
/**
* For plane masks (as used in {@link CullingVolume#computeVisibilityWithPlaneMask}), this special value
* represents the case where the object bounding volume is entirely outside the culling volume.
*
* @type {Number}
* @private
*/
CullingVolume.MASK_OUTSIDE = 0xffffffff;
/**
* For plane masks (as used in {@link CullingVolume.prototype.computeVisibilityWithPlaneMask}), this value
* represents the case where the object bounding volume is entirely inside the culling volume.
*
* @type {Number}
* @private
*/
CullingVolume.MASK_INSIDE = 0x00000000;
/**
* For plane masks (as used in {@link CullingVolume.prototype.computeVisibilityWithPlaneMask}), this value
* represents the case where the object bounding volume (may) intersect all planes of the culling volume.
*
* @type {Number}
* @private
*/
CullingVolume.MASK_INDETERMINATE = 0x7fffffff;
return CullingVolume;
});
/*global define*/
define('Scene/PerspectiveOffCenterFrustum',[
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Matrix4',
'./CullingVolume'
], function(
Cartesian3,
Cartesian4,
defined,
defineProperties,
DeveloperError,
Matrix4,
CullingVolume) {
'use strict';
/**
* The viewing frustum is defined by 6 planes.
* Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
* define the unit vector normal to the plane, and the w component is the distance of the
* plane from the origin/camera position.
*
* @alias PerspectiveOffCenterFrustum
* @constructor
*
*
* @example
* var frustum = new Cesium.PerspectiveOffCenterFrustum();
* frustum.right = 1.0;
* frustum.left = -1.0;
* frustum.top = 1.0;
* frustum.bottom = -1.0;
* frustum.near = 1.0;
* frustum.far = 2.0;
*
* @see PerspectiveFrustum
*/
function PerspectiveOffCenterFrustum() {
/**
* Defines the left clipping plane.
* @type {Number}
* @default undefined
*/
this.left = undefined;
this._left = undefined;
/**
* Defines the right clipping plane.
* @type {Number}
* @default undefined
*/
this.right = undefined;
this._right = undefined;
/**
* Defines the top clipping plane.
* @type {Number}
* @default undefined
*/
this.top = undefined;
this._top = undefined;
/**
* Defines the bottom clipping plane.
* @type {Number}
* @default undefined
*/
this.bottom = undefined;
this._bottom = undefined;
/**
* The distance of the near plane.
* @type {Number}
* @default 1.0
*/
this.near = 1.0;
this._near = this.near;
/**
* The distance of the far plane.
* @type {Number}
* @default 500000000.0
*/
this.far = 500000000.0;
this._far = this.far;
this._cullingVolume = new CullingVolume();
this._perspectiveMatrix = new Matrix4();
this._infinitePerspective = new Matrix4();
}
function update(frustum) {
if (!defined(frustum.right) || !defined(frustum.left) ||
!defined(frustum.top) || !defined(frustum.bottom) ||
!defined(frustum.near) || !defined(frustum.far)) {
throw new DeveloperError('right, left, top, bottom, near, or far parameters are not set.');
}
var t = frustum.top;
var b = frustum.bottom;
var r = frustum.right;
var l = frustum.left;
var n = frustum.near;
var f = frustum.far;
if (t !== frustum._top || b !== frustum._bottom ||
l !== frustum._left || r !== frustum._right ||
n !== frustum._near || f !== frustum._far) {
if (frustum.near <= 0 || frustum.near > frustum.far) {
throw new DeveloperError('near must be greater than zero and less than far.');
}
frustum._left = l;
frustum._right = r;
frustum._top = t;
frustum._bottom = b;
frustum._near = n;
frustum._far = f;
frustum._perspectiveMatrix = Matrix4.computePerspectiveOffCenter(l, r, b, t, n, f, frustum._perspectiveMatrix);
frustum._infinitePerspective = Matrix4.computeInfinitePerspectiveOffCenter(l, r, b, t, n, frustum._infinitePerspective);
}
}
defineProperties(PerspectiveOffCenterFrustum.prototype, {
/**
* Gets the perspective projection matrix computed from the view frustum.
* @memberof PerspectiveOffCenterFrustum.prototype
* @type {Matrix4}
* @readonly
*
* @see PerspectiveOffCenterFrustum#infiniteProjectionMatrix
*/
projectionMatrix : {
get : function() {
update(this);
return this._perspectiveMatrix;
}
},
/**
* Gets the perspective projection matrix computed from the view frustum with an infinite far plane.
* @memberof PerspectiveOffCenterFrustum.prototype
* @type {Matrix4}
* @readonly
*
* @see PerspectiveOffCenterFrustum#projectionMatrix
*/
infiniteProjectionMatrix : {
get : function() {
update(this);
return this._infinitePerspective;
}
}
});
var getPlanesRight = new Cartesian3();
var getPlanesNearCenter = new Cartesian3();
var getPlanesFarCenter = new Cartesian3();
var getPlanesNormal = new Cartesian3();
/**
* Creates a culling volume for this frustum.
*
* @param {Cartesian3} position The eye position.
* @param {Cartesian3} direction The view direction.
* @param {Cartesian3} up The up direction.
* @returns {CullingVolume} A culling volume at the given position and orientation.
*
* @example
* // Check if a bounding volume intersects the frustum.
* var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
* var intersect = cullingVolume.computeVisibility(boundingVolume);
*/
PerspectiveOffCenterFrustum.prototype.computeCullingVolume = function(position, direction, up) {
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
if (!defined(direction)) {
throw new DeveloperError('direction is required.');
}
if (!defined(up)) {
throw new DeveloperError('up is required.');
}
var planes = this._cullingVolume.planes;
var t = this.top;
var b = this.bottom;
var r = this.right;
var l = this.left;
var n = this.near;
var f = this.far;
var right = Cartesian3.cross(direction, up, getPlanesRight);
var nearCenter = getPlanesNearCenter;
Cartesian3.multiplyByScalar(direction, n, nearCenter);
Cartesian3.add(position, nearCenter, nearCenter);
var farCenter = getPlanesFarCenter;
Cartesian3.multiplyByScalar(direction, f, farCenter);
Cartesian3.add(position, farCenter, farCenter);
var normal = getPlanesNormal;
//Left plane computation
Cartesian3.multiplyByScalar(right, l, normal);
Cartesian3.add(nearCenter, normal, normal);
Cartesian3.subtract(normal, position, normal);
Cartesian3.normalize(normal, normal);
Cartesian3.cross(normal, up, normal);
var plane = planes[0];
if (!defined(plane)) {
plane = planes[0] = new Cartesian4();
}
plane.x = normal.x;
plane.y = normal.y;
plane.z = normal.z;
plane.w = -Cartesian3.dot(normal, position);
//Right plane computation
Cartesian3.multiplyByScalar(right, r, normal);
Cartesian3.add(nearCenter, normal, normal);
Cartesian3.subtract(normal, position, normal);
Cartesian3.normalize(normal, normal);
Cartesian3.cross(up, normal, normal);
plane = planes[1];
if (!defined(plane)) {
plane = planes[1] = new Cartesian4();
}
plane.x = normal.x;
plane.y = normal.y;
plane.z = normal.z;
plane.w = -Cartesian3.dot(normal, position);
//Bottom plane computation
Cartesian3.multiplyByScalar(up, b, normal);
Cartesian3.add(nearCenter, normal, normal);
Cartesian3.subtract(normal, position, normal);
Cartesian3.normalize(normal, normal);
Cartesian3.cross(right, normal, normal);
plane = planes[2];
if (!defined(plane)) {
plane = planes[2] = new Cartesian4();
}
plane.x = normal.x;
plane.y = normal.y;
plane.z = normal.z;
plane.w = -Cartesian3.dot(normal, position);
//Top plane computation
Cartesian3.multiplyByScalar(up, t, normal);
Cartesian3.add(nearCenter, normal, normal);
Cartesian3.subtract(normal, position, normal);
Cartesian3.normalize(normal, normal);
Cartesian3.cross(normal, right, normal);
plane = planes[3];
if (!defined(plane)) {
plane = planes[3] = new Cartesian4();
}
plane.x = normal.x;
plane.y = normal.y;
plane.z = normal.z;
plane.w = -Cartesian3.dot(normal, position);
//Near plane computation
plane = planes[4];
if (!defined(plane)) {
plane = planes[4] = new Cartesian4();
}
plane.x = direction.x;
plane.y = direction.y;
plane.z = direction.z;
plane.w = -Cartesian3.dot(direction, nearCenter);
//Far plane computation
Cartesian3.negate(direction, normal);
plane = planes[5];
if (!defined(plane)) {
plane = planes[5] = new Cartesian4();
}
plane.x = normal.x;
plane.y = normal.y;
plane.z = normal.z;
plane.w = -Cartesian3.dot(normal, farCenter);
return this._cullingVolume;
};
/**
* Returns the pixel's width and height in meters.
*
* @param {Number} drawingBufferWidth The width of the drawing buffer.
* @param {Number} drawingBufferHeight The height of the drawing buffer.
* @param {Number} distance The distance to the near plane in meters.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
*
* @exception {DeveloperError} drawingBufferWidth must be greater than zero.
* @exception {DeveloperError} drawingBufferHeight must be greater than zero.
*
* @example
* // Example 1
* // Get the width and height of a pixel.
* var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 1.0, new Cesium.Cartesian2());
*
* @example
* // Example 2
* // Get the width and height of a pixel if the near plane was set to 'distance'.
* // For example, get the size of a pixel of an image on a billboard.
* var position = camera.position;
* var direction = camera.direction;
* var toCenter = Cesium.Cartesian3.subtract(primitive.boundingVolume.center, position, new Cesium.Cartesian3()); // vector from camera to a primitive
* var toCenterProj = Cesium.Cartesian3.multiplyByScalar(direction, Cesium.Cartesian3.dot(direction, toCenter), new Cesium.Cartesian3()); // project vector onto camera direction vector
* var distance = Cesium.Cartesian3.magnitude(toCenterProj);
* var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, distance, new Cesium.Cartesian2());
*/
PerspectiveOffCenterFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) {
update(this);
if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) {
throw new DeveloperError('Both drawingBufferWidth and drawingBufferHeight are required.');
}
if (drawingBufferWidth <= 0) {
throw new DeveloperError('drawingBufferWidth must be greater than zero.');
}
if (drawingBufferHeight <= 0) {
throw new DeveloperError('drawingBufferHeight must be greater than zero.');
}
if (!defined(distance)) {
throw new DeveloperError('distance is required.');
}
if (!defined(result)) {
throw new DeveloperError('A result object is required.');
}
var inverseNear = 1.0 / this.near;
var tanTheta = this.top * inverseNear;
var pixelHeight = 2.0 * distance * tanTheta / drawingBufferHeight;
tanTheta = this.right * inverseNear;
var pixelWidth = 2.0 * distance * tanTheta / drawingBufferWidth;
result.x = pixelWidth;
result.y = pixelHeight;
return result;
};
/**
* Returns a duplicate of a PerspectiveOffCenterFrustum instance.
*
* @param {PerspectiveOffCenterFrustum} [result] The object onto which to store the result.
* @returns {PerspectiveOffCenterFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
*/
PerspectiveOffCenterFrustum.prototype.clone = function(result) {
if (!defined(result)) {
result = new PerspectiveOffCenterFrustum();
}
result.right = this.right;
result.left = this.left;
result.top = this.top;
result.bottom = this.bottom;
result.near = this.near;
result.far = this.far;
// force update of clone to compute matrices
result._left = undefined;
result._right = undefined;
result._top = undefined;
result._bottom = undefined;
result._near = undefined;
result._far = undefined;
return result;
};
/**
* Compares the provided PerspectiveOffCenterFrustum componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {PerspectiveOffCenterFrustum} [other] The right hand side PerspectiveOffCenterFrustum.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
PerspectiveOffCenterFrustum.prototype.equals = function(other) {
return (defined(other) &&
this.right === other.right &&
this.left === other.left &&
this.top === other.top &&
this.bottom === other.bottom &&
this.near === other.near &&
this.far === other.far);
};
return PerspectiveOffCenterFrustum;
});
/*global define*/
define('Scene/PerspectiveFrustum',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'./PerspectiveOffCenterFrustum'
], function(
defined,
defineProperties,
DeveloperError,
PerspectiveOffCenterFrustum) {
'use strict';
/**
* The viewing frustum is defined by 6 planes.
* Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
* define the unit vector normal to the plane, and the w component is the distance of the
* plane from the origin/camera position.
*
* @alias PerspectiveFrustum
* @constructor
*
*
* @example
* var frustum = new Cesium.PerspectiveFrustum();
* frustum.aspectRatio = canvas.clientWidth / canvas.clientHeight;
* frustum.fov = Cesium.Math.PI_OVER_THREE;
* frustum.near = 1.0;
* frustum.far = 2.0;
*
* @see PerspectiveOffCenterFrustum
*/
function PerspectiveFrustum() {
this._offCenterFrustum = new PerspectiveOffCenterFrustum();
/**
* The angle of the field of view (FOV), in radians. This angle will be used
* as the horizontal FOV if the width is greater than the height, otherwise
* it will be the vertical FOV.
* @type {Number}
* @default undefined
*/
this.fov = undefined;
this._fov = undefined;
this._fovy = undefined;
this._sseDenominator = undefined;
/**
* The aspect ratio of the frustum's width to it's height.
* @type {Number}
* @default undefined
*/
this.aspectRatio = undefined;
this._aspectRatio = undefined;
/**
* The distance of the near plane.
* @type {Number}
* @default 1.0
*/
this.near = 1.0;
this._near = this.near;
/**
* The distance of the far plane.
* @type {Number}
* @default 500000000.0
*/
this.far = 500000000.0;
this._far = this.far;
/**
* Offsets the frustum in the x direction.
* @type {Number}
* @default 0.0
*/
this.xOffset = 0.0;
this._xOffset = this.xOffset;
/**
* Offsets the frustum in the y direction.
* @type {Number}
* @default 0.0
*/
this.yOffset = 0.0;
this._yOffset = this.yOffset;
}
function update(frustum) {
if (!defined(frustum.fov) || !defined(frustum.aspectRatio) || !defined(frustum.near) || !defined(frustum.far)) {
throw new DeveloperError('fov, aspectRatio, near, or far parameters are not set.');
}
var f = frustum._offCenterFrustum;
if (frustum.fov !== frustum._fov || frustum.aspectRatio !== frustum._aspectRatio ||
frustum.near !== frustum._near || frustum.far !== frustum._far ||
frustum.xOffset !== frustum._xOffset || frustum.yOffset !== frustum._yOffset) {
if (frustum.fov < 0 || frustum.fov >= Math.PI) {
throw new DeveloperError('fov must be in the range [0, PI).');
}
if (frustum.aspectRatio < 0) {
throw new DeveloperError('aspectRatio must be positive.');
}
if (frustum.near < 0 || frustum.near > frustum.far) {
throw new DeveloperError('near must be greater than zero and less than far.');
}
frustum._aspectRatio = frustum.aspectRatio;
frustum._fov = frustum.fov;
frustum._fovy = (frustum.aspectRatio <= 1) ? frustum.fov : Math.atan(Math.tan(frustum.fov * 0.5) / frustum.aspectRatio) * 2.0;
frustum._near = frustum.near;
frustum._far = frustum.far;
frustum._sseDenominator = 2.0 * Math.tan(0.5 * frustum._fovy);
frustum._xOffset = frustum.xOffset;
frustum._yOffset = frustum.yOffset;
f.top = frustum.near * Math.tan(0.5 * frustum._fovy);
f.bottom = -f.top;
f.right = frustum.aspectRatio * f.top;
f.left = -f.right;
f.near = frustum.near;
f.far = frustum.far;
f.right += frustum.xOffset;
f.left += frustum.xOffset;
f.top += frustum.yOffset;
f.bottom += frustum.yOffset;
}
}
defineProperties(PerspectiveFrustum.prototype, {
/**
* Gets the perspective projection matrix computed from the view frustum.
* @memberof PerspectiveFrustum.prototype
* @type {Matrix4}
* @readonly
*
* @see PerspectiveFrustum#infiniteProjectionMatrix
*/
projectionMatrix : {
get : function() {
update(this);
return this._offCenterFrustum.projectionMatrix;
}
},
/**
* The perspective projection matrix computed from the view frustum with an infinite far plane.
* @memberof PerspectiveFrustum.prototype
* @type {Matrix4}
* @readonly
*
* @see PerspectiveFrustum#projectionMatrix
*/
infiniteProjectionMatrix : {
get : function() {
update(this);
return this._offCenterFrustum.infiniteProjectionMatrix;
}
},
/**
* Gets the angle of the vertical field of view, in radians.
* @memberof PerspectiveFrustum.prototype
* @type {Number}
* @readonly
* @default undefined
*/
fovy : {
get : function() {
update(this);
return this._fovy;
}
},
/**
* @readonly
* @private
*/
sseDenominator : {
get : function () {
update(this);
return this._sseDenominator;
}
}
});
/**
* Creates a culling volume for this frustum.
*
* @param {Cartesian3} position The eye position.
* @param {Cartesian3} direction The view direction.
* @param {Cartesian3} up The up direction.
* @returns {CullingVolume} A culling volume at the given position and orientation.
*
* @example
* // Check if a bounding volume intersects the frustum.
* var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
* var intersect = cullingVolume.computeVisibility(boundingVolume);
*/
PerspectiveFrustum.prototype.computeCullingVolume = function(position, direction, up) {
update(this);
return this._offCenterFrustum.computeCullingVolume(position, direction, up);
};
/**
* Returns the pixel's width and height in meters.
*
* @param {Number} drawingBufferWidth The width of the drawing buffer.
* @param {Number} drawingBufferHeight The height of the drawing buffer.
* @param {Number} distance The distance to the near plane in meters.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
*
* @exception {DeveloperError} drawingBufferWidth must be greater than zero.
* @exception {DeveloperError} drawingBufferHeight must be greater than zero.
*
* @example
* // Example 1
* // Get the width and height of a pixel.
* var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 1.0, new Cesium.Cartesian2());
*
* @example
* // Example 2
* // Get the width and height of a pixel if the near plane was set to 'distance'.
* // For example, get the size of a pixel of an image on a billboard.
* var position = camera.position;
* var direction = camera.direction;
* var toCenter = Cesium.Cartesian3.subtract(primitive.boundingVolume.center, position, new Cesium.Cartesian3()); // vector from camera to a primitive
* var toCenterProj = Cesium.Cartesian3.multiplyByScalar(direction, Cesium.Cartesian3.dot(direction, toCenter), new Cesium.Cartesian3()); // project vector onto camera direction vector
* var distance = Cesium.Cartesian3.magnitude(toCenterProj);
* var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, distance, new Cesium.Cartesian2());
*/
PerspectiveFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) {
update(this);
return this._offCenterFrustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, result);
};
/**
* Returns a duplicate of a PerspectiveFrustum instance.
*
* @param {PerspectiveFrustum} [result] The object onto which to store the result.
* @returns {PerspectiveFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
*/
PerspectiveFrustum.prototype.clone = function(result) {
if (!defined(result)) {
result = new PerspectiveFrustum();
}
result.aspectRatio = this.aspectRatio;
result.fov = this.fov;
result.near = this.near;
result.far = this.far;
// force update of clone to compute matrices
result._aspectRatio = undefined;
result._fov = undefined;
result._near = undefined;
result._far = undefined;
this._offCenterFrustum.clone(result._offCenterFrustum);
return result;
};
/**
* Compares the provided PerspectiveFrustum componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {PerspectiveFrustum} [other] The right hand side PerspectiveFrustum.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
PerspectiveFrustum.prototype.equals = function(other) {
if (!defined(other)) {
return false;
}
update(this);
update(other);
return (this.fov === other.fov &&
this.aspectRatio === other.aspectRatio &&
this.near === other.near &&
this.far === other.far &&
this._offCenterFrustum.equals(other._offCenterFrustum));
};
return PerspectiveFrustum;
});
/*global define*/
define('Scene/CameraFlightPath',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/EasingFunction',
'../Core/Math',
'./PerspectiveFrustum',
'./PerspectiveOffCenterFrustum',
'./SceneMode'
], function(
Cartesian2,
Cartesian3,
Cartographic,
defaultValue,
defined,
DeveloperError,
EasingFunction,
CesiumMath,
PerspectiveFrustum,
PerspectiveOffCenterFrustum,
SceneMode) {
'use strict';
/**
* Creates tweens for camera flights.
*
* Mouse interaction is disabled during flights.
*
* @private
*/
var CameraFlightPath = {
};
function getAltitude(frustum, dx, dy) {
var near;
var top;
var right;
if (frustum instanceof PerspectiveFrustum) {
var tanTheta = Math.tan(0.5 * frustum.fovy);
near = frustum.near;
top = frustum.near * tanTheta;
right = frustum.aspectRatio * top;
return Math.max(dx * near / right, dy * near / top);
} else if (frustum instanceof PerspectiveOffCenterFrustum) {
near = frustum.near;
top = frustum.top;
right = frustum.right;
return Math.max(dx * near / right, dy * near / top);
}
return Math.max(dx, dy);
}
var scratchCart = new Cartesian3();
var scratchCart2 = new Cartesian3();
function createHeightFunction(camera, destination, startHeight, endHeight, optionAltitude) {
var altitude = optionAltitude;
var maxHeight = Math.max(startHeight, endHeight);
if (!defined(altitude)) {
var start = camera.position;
var end = destination;
var up = camera.up;
var right = camera.right;
var frustum = camera.frustum;
var diff = Cartesian3.subtract(start, end, scratchCart);
var verticalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2));
var horizontalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(right, Cartesian3.dot(diff, right), scratchCart2));
altitude = Math.min(getAltitude(frustum, verticalDistance, horizontalDistance) * 0.20, 1000000000.0);
}
if (maxHeight < altitude) {
var power = 8.0;
var factor = 1000000.0;
var s = -Math.pow((altitude - startHeight) * factor, 1.0 / power);
var e = Math.pow((altitude - endHeight) * factor, 1.0 / power);
return function(t) {
var x = t * (e - s) + s;
return -Math.pow(x, power) / factor + altitude;
};
}
return function(t) {
return CesiumMath.lerp(startHeight, endHeight, t);
};
}
function adjustAngleForLERP(startAngle, endAngle) {
if (CesiumMath.equalsEpsilon(startAngle, CesiumMath.TWO_PI, CesiumMath.EPSILON11)) {
startAngle = 0.0;
}
if (endAngle > startAngle + Math.PI) {
startAngle += CesiumMath.TWO_PI;
} else if (endAngle < startAngle - Math.PI) {
startAngle -= CesiumMath.TWO_PI;
}
return startAngle;
}
var scratchStart = new Cartesian3();
function createUpdateCV(scene, duration, destination, heading, pitch, roll, optionAltitude) {
var camera = scene.camera;
var start = Cartesian3.clone(camera.position, scratchStart);
var startPitch = camera.pitch;
var startHeading = adjustAngleForLERP(camera.heading, heading);
var startRoll = adjustAngleForLERP(camera.roll, roll);
var heightFunction = createHeightFunction(camera, destination, start.z, destination.z, optionAltitude);
function update(value) {
var time = value.time / duration;
camera.setView({
orientation: {
heading : CesiumMath.lerp(startHeading, heading, time),
pitch : CesiumMath.lerp(startPitch, pitch, time),
roll : CesiumMath.lerp(startRoll, roll, time)
}
});
Cartesian2.lerp(start, destination, time, camera.position);
camera.position.z = heightFunction(time);
}
return update;
}
var scratchStartCart = new Cartographic();
var scratchEndCart = new Cartographic();
function createUpdate3D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
var camera = scene.camera;
var projection = scene.mapProjection;
var ellipsoid = projection.ellipsoid;
var startCart = Cartographic.clone(camera.positionCartographic, scratchStartCart);
var startPitch = camera.pitch;
var startHeading = adjustAngleForLERP(camera.heading, heading);
var startRoll = adjustAngleForLERP(camera.roll, roll);
var destCart = ellipsoid.cartesianToCartographic(destination, scratchEndCart);
startCart.longitude = CesiumMath.zeroToTwoPi(startCart.longitude);
destCart.longitude = CesiumMath.zeroToTwoPi(destCart.longitude);
var diff = startCart.longitude - destCart.longitude;
if (diff < -CesiumMath.PI) {
startCart.longitude += CesiumMath.TWO_PI;
} else if (diff > CesiumMath.PI) {
destCart.longitude += CesiumMath.TWO_PI;
}
var heightFunction = createHeightFunction(camera, destination, startCart.height, destCart.height, optionAltitude);
function update(value) {
var time = value.time / duration;
var position = Cartesian3.fromRadians(
CesiumMath.lerp(startCart.longitude, destCart.longitude, time),
CesiumMath.lerp(startCart.latitude, destCart.latitude, time),
heightFunction(time)
);
camera.setView({
destination : position,
orientation: {
heading : CesiumMath.lerp(startHeading, heading, time),
pitch : CesiumMath.lerp(startPitch, pitch, time),
roll : CesiumMath.lerp(startRoll, roll, time)
}
});
}
return update;
}
function createUpdate2D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
var camera = scene.camera;
var start = Cartesian3.clone(camera.position, scratchStart);
var startHeading = adjustAngleForLERP(camera.heading, heading);
var startHeight = camera.frustum.right - camera.frustum.left;
var heightFunction = createHeightFunction(camera, destination, startHeight, destination.z, optionAltitude);
function update(value) {
var time = value.time / duration;
camera.setView({
orientation: {
heading : CesiumMath.lerp(startHeading, heading, time)
}
});
Cartesian2.lerp(start, destination, time, camera.position);
var zoom = heightFunction(time);
var frustum = camera.frustum;
var ratio = frustum.top / frustum.right;
var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
frustum.right += incrementAmount;
frustum.left -= incrementAmount;
frustum.top = ratio * frustum.right;
frustum.bottom = -frustum.top;
}
return update;
}
var scratchCartographic = new Cartographic();
var scratchDestination = new Cartesian3();
function emptyFlight(complete, cancel) {
return {
startObject : {},
stopObject : {},
duration : 0.0,
complete : complete,
cancel : cancel
};
}
function wrapCallback(controller, cb) {
function wrapped() {
if (typeof cb === 'function') {
cb();
}
controller.enableInputs = true;
}
return wrapped;
}
CameraFlightPath.createTween = function(scene, options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var destination = options.destination;
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(destination)) {
throw new DeveloperError('destination is required.');
}
var mode = scene.mode;
if (mode === SceneMode.MORPHING) {
return emptyFlight();
}
var convert = defaultValue(options.convert, true);
var projection = scene.mapProjection;
var ellipsoid = projection.ellipsoid;
var maximumHeight = options.maximumHeight;
var easingFunction = options.easingFunction;
if (convert && mode !== SceneMode.SCENE3D) {
ellipsoid.cartesianToCartographic(destination, scratchCartographic);
destination = projection.project(scratchCartographic, scratchDestination);
}
var camera = scene.camera;
var transform = options.endTransform;
if (defined(transform)) {
camera._setTransform(transform);
}
var duration = options.duration;
if (!defined(duration)) {
duration = Math.ceil(Cartesian3.distance(camera.position, destination) / 1000000.0) + 2.0;
duration = Math.min(duration, 3.0);
}
var heading = defaultValue(options.heading, 0.0);
var pitch = defaultValue(options.pitch, -CesiumMath.PI_OVER_TWO);
var roll = defaultValue(options.roll, 0.0);
var controller = scene.screenSpaceCameraController;
controller.enableInputs = false;
var complete = wrapCallback(controller, options.complete);
var cancel = wrapCallback(controller, options.cancel);
var frustum = camera.frustum;
var empty = scene.mode === SceneMode.SCENE2D;
empty = empty && Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6);
empty = empty && CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6);
empty = empty || (scene.mode !== SceneMode.SCENE2D &&
Cartesian3.equalsEpsilon(destination, camera.position, CesiumMath.EPSILON10));
empty = empty &&
CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(heading), CesiumMath.negativePiToPi(camera.heading), CesiumMath.EPSILON10) &&
CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(pitch), CesiumMath.negativePiToPi(camera.pitch), CesiumMath.EPSILON10) &&
CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(roll), CesiumMath.negativePiToPi(camera.roll), CesiumMath.EPSILON10);
if (empty) {
return emptyFlight(complete, cancel);
}
var updateFunctions = new Array(4);
updateFunctions[SceneMode.SCENE2D] = createUpdate2D;
updateFunctions[SceneMode.SCENE3D] = createUpdate3D;
updateFunctions[SceneMode.COLUMBUS_VIEW] = createUpdateCV;
if (duration <= 0.0) {
var newOnComplete = function() {
var update = updateFunctions[mode](scene, 1.0, destination, heading, pitch, roll, maximumHeight);
update({ time: 1.0 });
if (typeof complete === 'function') {
complete();
}
};
return emptyFlight(newOnComplete, cancel);
}
var update = updateFunctions[mode](scene, duration, destination, heading, pitch, roll, maximumHeight);
if (!defined(easingFunction)) {
var startHeight = camera.positionCartographic.height;
var endHeight = mode === SceneMode.SCENE3D ? ellipsoid.cartesianToCartographic(destination).height : destination.z;
if (startHeight > endHeight && startHeight > 11500.0) {
easingFunction = EasingFunction.CUBIC_OUT;
} else {
easingFunction = EasingFunction.QUINTIC_IN_OUT;
}
}
return {
duration : duration,
easingFunction : easingFunction,
startObject : {
time : 0.0
},
stopObject : {
time : duration
},
update : update,
complete : complete,
cancel: cancel
};
};
return CameraFlightPath;
});
/*global define*/
define('Scene/MapMode2D',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* Describes how the map will operate in 2D.
*
* @exports MapMode2D
*/
var MapMode2D = {
/**
* The 2D map can be rotated about the z axis.
*
* @type {Number}
* @constant
*/
ROTATE : 0,
/**
* The 2D map can be scrolled infinitely in the horizontal direction.
*
* @type {Number}
* @constant
*/
INFINITE_SCROLL : 1
};
return freezeObject(MapMode2D);
});
/*global define*/
define('Scene/Camera',[
'../Core/BoundingSphere',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/EasingFunction',
'../Core/Ellipsoid',
'../Core/EllipsoidGeodesic',
'../Core/Event',
'../Core/HeadingPitchRange',
'../Core/Intersect',
'../Core/IntersectionTests',
'../Core/Math',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/Quaternion',
'../Core/Ray',
'../Core/Rectangle',
'../Core/Transforms',
'./CameraFlightPath',
'./MapMode2D',
'./PerspectiveFrustum',
'./SceneMode'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
EasingFunction,
Ellipsoid,
EllipsoidGeodesic,
Event,
HeadingPitchRange,
Intersect,
IntersectionTests,
CesiumMath,
Matrix3,
Matrix4,
Quaternion,
Ray,
Rectangle,
Transforms,
CameraFlightPath,
MapMode2D,
PerspectiveFrustum,
SceneMode) {
'use strict';
/**
* The camera is defined by a position, orientation, and view frustum.
*
* The orientation forms an orthonormal basis with a view, up and right = view x up unit vectors.
*
* The viewing frustum is defined by 6 planes.
* Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
* define the unit vector normal to the plane, and the w component is the distance of the
* plane from the origin/camera position.
*
* @alias Camera
*
* @constructor
*
* @param {Scene} scene The scene.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Camera.html|Cesium Sandcastle Camera Demo}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Camera%20Tutorial.html">Sandcastle Example from the = (prevAngle + Math.PI)) {
angle -= TwoPI;
}
while (angle < (prevAngle - Math.PI)) {
angle += TwoPI;
}
movement.angleAndHeight.endPosition.x = -angle * canvas.clientWidth / 12;
movement.angleAndHeight.startPosition.x = -prevAngle * canvas.clientWidth / 12;
}
}, ScreenSpaceEventType.PINCH_MOVE, modifier);
}
function listenToWheel(aggregator, modifier) {
var key = getKey(CameraEventType.WHEEL, modifier);
var update = aggregator._update;
update[key] = true;
var movement = aggregator._movement[key];
if (!defined(movement)) {
movement = aggregator._movement[key] = {};
}
movement.startPosition = new Cartesian2();
movement.endPosition = new Cartesian2();
aggregator._eventHandler.setInputAction(function(delta) {
// TODO: magic numbers
var arcLength = 15.0 * CesiumMath.toRadians(delta);
if (!update[key]) {
movement.endPosition.y = movement.endPosition.y + arcLength;
} else {
Cartesian2.clone(Cartesian2.ZERO, movement.startPosition);
movement.endPosition.x = 0.0;
movement.endPosition.y = arcLength;
update[key] = false;
}
}, ScreenSpaceEventType.WHEEL, modifier);
}
function listenMouseButtonDownUp(aggregator, modifier, type) {
var key = getKey(type, modifier);
var isDown = aggregator._isDown;
var eventStartPosition = aggregator._eventStartPosition;
var pressTime = aggregator._pressTime;
var releaseTime = aggregator._releaseTime;
isDown[key] = false;
eventStartPosition[key] = new Cartesian2();
var lastMovement = aggregator._lastMovement[key];
if (!defined(lastMovement)) {
lastMovement = aggregator._lastMovement[key] = {
startPosition : new Cartesian2(),
endPosition : new Cartesian2(),
valid : false
};
}
var down;
var up;
if (type === CameraEventType.LEFT_DRAG) {
down = ScreenSpaceEventType.LEFT_DOWN;
up = ScreenSpaceEventType.LEFT_UP;
} else if (type === CameraEventType.RIGHT_DRAG) {
down = ScreenSpaceEventType.RIGHT_DOWN;
up = ScreenSpaceEventType.RIGHT_UP;
} else if (type === CameraEventType.MIDDLE_DRAG) {
down = ScreenSpaceEventType.MIDDLE_DOWN;
up = ScreenSpaceEventType.MIDDLE_UP;
}
aggregator._eventHandler.setInputAction(function(event) {
aggregator._buttonsDown++;
lastMovement.valid = false;
isDown[key] = true;
pressTime[key] = new Date();
Cartesian2.clone(event.position, eventStartPosition[key]);
}, down, modifier);
aggregator._eventHandler.setInputAction(function() {
aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
isDown[key] = false;
releaseTime[key] = new Date();
}, up, modifier);
}
function cloneMouseMovement(mouseMovement, result) {
Cartesian2.clone(mouseMovement.startPosition, result.startPosition);
Cartesian2.clone(mouseMovement.endPosition, result.endPosition);
}
function listenMouseMove(aggregator, modifier) {
var update = aggregator._update;
var movement = aggregator._movement;
var lastMovement = aggregator._lastMovement;
var isDown = aggregator._isDown;
for ( var typeName in CameraEventType) {
if (CameraEventType.hasOwnProperty(typeName)) {
var type = CameraEventType[typeName];
if (defined(type)) {
var key = getKey(type, modifier);
update[key] = true;
if (!defined(aggregator._lastMovement[key])) {
aggregator._lastMovement[key] = {
startPosition : new Cartesian2(),
endPosition : new Cartesian2(),
valid : false
};
}
if (!defined(aggregator._movement[key])) {
aggregator._movement[key] = {
startPosition : new Cartesian2(),
endPosition : new Cartesian2()
};
}
}
}
}
aggregator._eventHandler.setInputAction(function(mouseMovement) {
for ( var typeName in CameraEventType) {
if (CameraEventType.hasOwnProperty(typeName)) {
var type = CameraEventType[typeName];
if (defined(type)) {
var key = getKey(type, modifier);
if (isDown[key]) {
if (!update[key]) {
Cartesian2.clone(mouseMovement.endPosition, movement[key].endPosition);
} else {
cloneMouseMovement(movement[key], lastMovement[key]);
lastMovement[key].valid = true;
cloneMouseMovement(mouseMovement, movement[key]);
update[key] = false;
}
}
}
}
}
Cartesian2.clone(mouseMovement.endPosition, aggregator._currentMousePosition);
}, ScreenSpaceEventType.MOUSE_MOVE, modifier);
}
/**
* Aggregates input events. For example, suppose the following inputs are received between frames:
* left mouse button down, mouse move, mouse move, left mouse button up. These events will be aggregated into
* one event with a start and end position of the mouse.
*
* @alias CameraEventAggregator
* @constructor
*
* @param {Canvas} [element=document] The element to handle events for.
*
* @see ScreenSpaceEventHandler
*/
function CameraEventAggregator(canvas) {
if (!defined(canvas)) {
throw new DeveloperError('canvas is required.');
}
this._eventHandler = new ScreenSpaceEventHandler(canvas, true);
this._update = {};
this._movement = {};
this._lastMovement = {};
this._isDown = {};
this._eventStartPosition = {};
this._pressTime = {};
this._releaseTime = {};
this._buttonsDown = 0;
this._currentMousePosition = new Cartesian2();
listenToWheel(this, undefined);
listenToPinch(this, undefined, canvas);
listenMouseButtonDownUp(this, undefined, CameraEventType.LEFT_DRAG);
listenMouseButtonDownUp(this, undefined, CameraEventType.RIGHT_DRAG);
listenMouseButtonDownUp(this, undefined, CameraEventType.MIDDLE_DRAG);
listenMouseMove(this, undefined);
for ( var modifierName in KeyboardEventModifier) {
if (KeyboardEventModifier.hasOwnProperty(modifierName)) {
var modifier = KeyboardEventModifier[modifierName];
if (defined(modifier)) {
listenToWheel(this, modifier);
listenToPinch(this, modifier, canvas);
listenMouseButtonDownUp(this, modifier, CameraEventType.LEFT_DRAG);
listenMouseButtonDownUp(this, modifier, CameraEventType.RIGHT_DRAG);
listenMouseButtonDownUp(this, modifier, CameraEventType.MIDDLE_DRAG);
listenMouseMove(this, modifier);
}
}
}
}
defineProperties(CameraEventAggregator.prototype, {
/**
* Gets the current mouse position.
* @memberof CameraEventAggregator.prototype
* @type {Cartesian2}
*/
currentMousePosition : {
get : function() {
return this._currentMousePosition;
}
},
/**
* Gets whether any mouse button is down, a touch has started, or the wheel has been moved.
* @memberof CameraEventAggregator.prototype
* @type {Boolean}
*/
anyButtonDown : {
get : function() {
var wheelMoved = !this._update[getKey(CameraEventType.WHEEL)] ||
!this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.SHIFT)] ||
!this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.CTRL)] ||
!this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.ALT)];
return this._buttonsDown > 0 || wheelMoved;
}
}
});
/**
* Gets if a mouse button down or touch has started and has been moved.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Boolean} Returns true
if a mouse button down or touch has started and has been moved; otherwise, false
*/
CameraEventAggregator.prototype.isMoving = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getKey(type, modifier);
return !this._update[key];
};
/**
* Gets the aggregated start and end position of the current event.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Object} An object with two {@link Cartesian2} properties: startPosition
and endPosition
.
*/
CameraEventAggregator.prototype.getMovement = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getKey(type, modifier);
var movement = this._movement[key];
return movement;
};
/**
* Gets the start and end position of the last move event (not the aggregated event).
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Object|undefined} An object with two {@link Cartesian2} properties: startPosition
and endPosition
or undefined
.
*/
CameraEventAggregator.prototype.getLastMovement = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getKey(type, modifier);
var lastMovement = this._lastMovement[key];
if (lastMovement.valid) {
return lastMovement;
}
return undefined;
};
/**
* Gets whether the mouse button is down or a touch has started.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Boolean} Whether the mouse button is down or a touch has started.
*/
CameraEventAggregator.prototype.isButtonDown = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getKey(type, modifier);
return this._isDown[key];
};
/**
* Gets the mouse position that started the aggregation.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Cartesian2} The mouse position.
*/
CameraEventAggregator.prototype.getStartMousePosition = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
if (type === CameraEventType.WHEEL) {
return this._currentMousePosition;
}
var key = getKey(type, modifier);
return this._eventStartPosition[key];
};
/**
* Gets the time the button was pressed or the touch was started.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Date} The time the button was pressed or the touch was started.
*/
CameraEventAggregator.prototype.getButtonPressTime = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getKey(type, modifier);
return this._pressTime[key];
};
/**
* Gets the time the button was released or the touch was ended.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Date} The time the button was released or the touch was ended.
*/
CameraEventAggregator.prototype.getButtonReleaseTime = function(type, modifier) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
var key = getKey(type, modifier);
return this._releaseTime[key];
};
/**
* Signals that all of the events have been handled and the aggregator should be reset to handle new events.
*/
CameraEventAggregator.prototype.reset = function() {
for ( var name in this._update) {
if (this._update.hasOwnProperty(name)) {
this._update[name] = true;
}
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see CameraEventAggregator#destroy
*/
CameraEventAggregator.prototype.isDestroyed = function() {
return false;
};
/**
* Removes mouse listeners held by this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* handler = handler && handler.destroy();
*
* @see CameraEventAggregator#isDestroyed
*/
CameraEventAggregator.prototype.destroy = function() {
this._eventHandler = this._eventHandler && this._eventHandler.destroy();
return destroyObject(this);
};
return CameraEventAggregator;
});
/*global define*/
define('Scene/UrlTemplateImageryProvider',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/combine',
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/GeographicTilingScheme',
'../Core/isArray',
'../Core/loadJson',
'../Core/loadText',
'../Core/loadWithXhr',
'../Core/loadXML',
'../Core/Math',
'../Core/Rectangle',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/when',
'./ImageryProvider'
], function(
Cartesian2,
Cartesian3,
Cartographic,
combine,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
GeographicTilingScheme,
isArray,
loadJson,
loadText,
loadWithXhr,
loadXML,
CesiumMath,
Rectangle,
WebMercatorTilingScheme,
when,
ImageryProvider) {
'use strict';
/**
* Provides imagery by requesting tiles using a specified URL template.
*
* @alias UrlTemplateImageryProvider
* @constructor
*
* @param {Promise.|Object} [options] Object with the following properties:
* @param {String} options.url The URL template to use to request tiles. It has the following keywords:
*
* {z}
: The level of the tile in the tiling scheme. Level zero is the root of the quadtree pyramid.
* {x}
: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.
* {y}
: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.
* {s}
: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.
* {reverseX}
: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.
* {reverseY}
: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.
* {reverseZ}
: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid. In order to use reverseZ, maximumLevel must be defined.
* {westDegrees}
: The Western edge of the tile in geodetic degrees.
* {southDegrees}
: The Southern edge of the tile in geodetic degrees.
* {eastDegrees}
: The Eastern edge of the tile in geodetic degrees.
* {northDegrees}
: The Northern edge of the tile in geodetic degrees.
* {westProjected}
: The Western edge of the tile in projected coordinates of the tiling scheme.
* {southProjected}
: The Southern edge of the tile in projected coordinates of the tiling scheme.
* {eastProjected}
: The Eastern edge of the tile in projected coordinates of the tiling scheme.
* {northProjected}
: The Northern edge of the tile in projected coordinates of the tiling scheme.
* {width}
: The width of each tile in pixels.
* {height}
: The height of each tile in pixels.
*
* @param {String} [options.pickFeaturesUrl] The URL template to use to pick features. If this property is not specified,
* {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
* features picked. The URL template supports all of the keywords supported by the url
* parameter, plus the following:
*
* {i}
: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.
* {j}
: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.
* {reverseI}
: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.
* {reverseJ}
: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.
* {longitudeDegrees}
: The longitude of the picked position in degrees.
* {latitudeDegrees}
: The latitude of the picked position in degrees.
* {longitudeProjected}
: The longitude of the picked position in the projected coordinates of the tiling scheme.
* {latitudeProjected}
: The latitude of the picked position in the projected coordinates of the tiling scheme.
* {format}
: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.
*
* @param {Object} [options.urlSchemeZeroPadding] Gets the URL scheme zero padding for each tile coordinate. The format is '000' where
* each coordinate will be padded on the left with zeros to match the width of the passed string of zeros. e.g. Setting:
* urlSchemeZeroPadding : { '{x}' : '0000'}
* will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
* It the passed object has the following keywords:
*
* {z}
: The zero padding for the level of the tile in the tiling scheme.
* {x}
: The zero padding for the tile X coordinate in the tiling scheme.
* {y}
: The zero padding for the the tile Y coordinate in the tiling scheme.
* {reverseX}
: The zero padding for the tile reverseX coordinate in the tiling scheme.
* {reverseY}
: The zero padding for the tile reverseY coordinate in the tiling scheme.
* {reverseZ}
: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.
*
* @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the {s}
placeholder in the URL template.
* If this parameter is a single string, each character in the string is a subdomain. If it is
* an array, each element in the array is a subdomain.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
* @param {Credit|String} [options.credit=''] A credit for the data source, which is displayed on the canvas.
* @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
* to result in rendering problems.
* @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
* @param {TilingScheme} [options.tilingScheme=WebMercatorTilingScheme] The tiling scheme specifying how the ellipsoidal
* surface is broken into tiles. If this parameter is not provided, a {@link WebMercatorTilingScheme}
* is used.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
* parameter is specified, the WGS84 ellipsoid is used.
* @param {Number} [options.tileWidth=256] Pixel width of image tiles.
* @param {Number} [options.tileHeight=256] Pixel height of image tiles.
* @param {Boolean} [options.hasAlphaChannel=true] true if the images provided by this imagery provider
* include an alpha channel; otherwise, false. If this property is false, an alpha channel, if
* present, will be ignored. If this property is true, any images without an alpha channel will
* be treated as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are potentially reduced.
* @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats] The formats in which to get feature information at a
* specific location when {@link UrlTemplateImageryProvider#pickFeatures} is invoked. If this
* parameter is not specified, feature picking is disabled.
* @param {Boolean} [options.enablePickFeatures=true] If true, {@link UrlTemplateImageryProvider#pickFeatures} will
* request the options.pickFeaturesUrl
and attempt to interpret the features included in the response. If false,
* {@link UrlTemplateImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
* features) without communicating with the server. Set this property to false if you know your data
* source does not support picking features or if you don't want this provider's features to be pickable. Note
* that this can be dynamically overridden by modifying the {@link UriTemplateImageryProvider#enablePickFeatures}
* property.
*
*
* @example
* // Access Natural Earth II imagery, which uses a TMS tiling scheme and Geographic (EPSG:4326) project
* var tms = new Cesium.UrlTemplateImageryProvider({
* url : 'https://cesiumjs.org/tilesets/imagery/naturalearthii/{z}/{x}/{reverseY}.jpg',
* credit : '© Analytical Graphics, Inc.',
* tilingScheme : new Cesium.GeographicTilingScheme(),
* maximumLevel : 5
* });
* // Access the CartoDB Positron basemap, which uses an OpenStreetMap-like tiling scheme.
* var positron = new Cesium.UrlTemplateImageryProvider({
* url : 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
* credit : 'Map tiles by CartoDB, under CC BY 3.0. Data by OpenStreetMap, under ODbL.'
* });
* // Access a Web Map Service (WMS) server.
* var wms = new Cesium.UrlTemplateImageryProvider({
* url : 'https://programs.communications.gov.au/geoserver/ows?tiled=true&' +
* 'transparent=true&format=image%2Fpng&exceptions=application%2Fvnd.ogc.se_xml&' +
* 'styles=&service=WMS&version=1.1.1&request=GetMap&' +
* 'layers=public%3AMyBroadband_Availability&srs=EPSG%3A3857&' +
* 'bbox={westProjected}%2C{southProjected}%2C{eastProjected}%2C{northProjected}&' +
* 'width=256&height=256',
* rectangle : Cesium.Rectangle.fromDegrees(96.799393, -43.598214999057824, 153.63925700000001, -9.2159219997013)
* });
*
* @see ArcGisMapServerImageryProvider
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see createOpenStreetMapImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
*/
function UrlTemplateImageryProvider(options) {
if (!defined(options)) {
throw new DeveloperError('options is required.');
}
if (!when.isPromise(options) && !defined(options.url)) {
throw new DeveloperError('options is required.');
}
this._errorEvent = new Event();
this._url = undefined;
this._urlSchemeZeroPadding = undefined;
this._pickFeaturesUrl = undefined;
this._proxy = undefined;
this._tileWidth = undefined;
this._tileHeight = undefined;
this._maximumLevel = undefined;
this._minimumLevel = undefined;
this._tilingScheme = undefined;
this._rectangle = undefined;
this._tileDiscardPolicy = undefined;
this._credit = undefined;
this._hasAlphaChannel = undefined;
this._readyPromise = undefined;
/**
* Gets or sets a value indicating whether feature picking is enabled. If true, {@link UrlTemplateImageryProvider#pickFeatures} will
* request the options.pickFeaturesUrl
and attempt to interpret the features included in the response. If false,
* {@link UrlTemplateImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
* features) without communicating with the server. Set this property to false if you know your data
* source does not support picking features or if you don't want this provider's features to be pickable.
* @type {Boolean}
* @default true
*/
this.enablePickFeatures = true;
this.reinitialize(options);
}
defineProperties(UrlTemplateImageryProvider.prototype, {
/**
* Gets the URL template to use to request tiles. It has the following keywords:
*
* {z}
: The level of the tile in the tiling scheme. Level zero is the root of the quadtree pyramid.
* {x}
: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.
* {y}
: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.
* {s}
: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.
* {reverseX}
: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.
* {reverseY}
: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.
* {reverseZ}
: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid. In order to use reverseZ, maximumLevel must be defined.
* {westDegrees}
: The Western edge of the tile in geodetic degrees.
* {southDegrees}
: The Southern edge of the tile in geodetic degrees.
* {eastDegrees}
: The Eastern edge of the tile in geodetic degrees.
* {northDegrees}
: The Northern edge of the tile in geodetic degrees.
* {westProjected}
: The Western edge of the tile in projected coordinates of the tiling scheme.
* {southProjected}
: The Southern edge of the tile in projected coordinates of the tiling scheme.
* {eastProjected}
: The Eastern edge of the tile in projected coordinates of the tiling scheme.
* {northProjected}
: The Northern edge of the tile in projected coordinates of the tiling scheme.
* {width}
: The width of each tile in pixels.
* {height}
: The height of each tile in pixels.
*
* @memberof UrlTemplateImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the URL scheme zero padding for each tile coordinate. The format is '000' where each coordinate will be padded on
* the left with zeros to match the width of the passed string of zeros. e.g. Setting:
* urlSchemeZeroPadding : { '{x}' : '0000'}
* will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
* It has the following keywords:
*
* {z}
: The zero padding for the level of the tile in the tiling scheme.
* {x}
: The zero padding for the tile X coordinate in the tiling scheme.
* {y}
: The zero padding for the the tile Y coordinate in the tiling scheme.
* {reverseX}
: The zero padding for the tile reverseX coordinate in the tiling scheme.
* {reverseY}
: The zero padding for the tile reverseY coordinate in the tiling scheme.
* {reverseZ}
: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.
*
* @memberof UrlTemplateImageryProvider.prototype
* @type {Object}
* @readonly
*/
urlSchemeZeroPadding : {
get : function() {
return this._urlSchemeZeroPadding;
}
},
/**
* Gets the URL template to use to use to pick features. If this property is not specified,
* {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
* features picked. The URL template supports all of the keywords supported by the
* {@link UrlTemplateImageryProvider#url} property, plus the following:
*
* {i}
: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.
* {j}
: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.
* {reverseI}
: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.
* {reverseJ}
: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.
* {longitudeDegrees}
: The longitude of the picked position in degrees.
* {latitudeDegrees}
: The latitude of the picked position in degrees.
* {longitudeProjected}
: The longitude of the picked position in the projected coordinates of the tiling scheme.
* {latitudeProjected}
: The latitude of the picked position in the projected coordinates of the tiling scheme.
* {format}
: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.
*
* @memberof UrlTemplateImageryProvider.prototype
* @type {String}
* @readonly
*/
pickFeaturesUrl : {
get : function() {
return this._pickFeaturesUrl;
}
},
/**
* Gets the proxy used by this provider.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Proxy}
* @readonly
* @default undefined
*/
proxy : {
get : function() {
return this._proxy;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Number}
* @readonly
* @default 256
*/
tileWidth : {
get : function() {
if (!this.ready) {
throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
}
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Number}
* @readonly
* @default 256
*/
tileHeight: {
get : function() {
if (!this.ready) {
throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
}
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested, or undefined if there is no limit.
* This function should not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Number}
* @readonly
* @default undefined
*/
maximumLevel : {
get : function() {
if (!this.ready) {
throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
}
return this._maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Number}
* @readonly
* @default 0
*/
minimumLevel : {
get : function() {
if (!this.ready) {
throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
}
return this._minimumLevel;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {TilingScheme}
* @readonly
* @default new WebMercatorTilingScheme()
*/
tilingScheme : {
get : function() {
if (!this.ready) {
throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
}
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Rectangle}
* @readonly
* @default tilingScheme.rectangle
*/
rectangle : {
get : function() {
if (!this.ready) {
throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
}
return this._rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
* @default undefined
*/
tileDiscardPolicy : {
get : function() {
if (!this.ready) {
throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
}
return this._tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return defined(this._urlParts);
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link UrlTemplateImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Credit}
* @readonly
* @default undefined
*/
credit : {
get : function() {
if (!this.ready) {
throw new DeveloperError('credit must not be called before the imagery provider is ready.');
}
return this._credit;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced. This function should
* not be called before {@link ImageryProvider#ready} returns true.
* @memberof UrlTemplateImageryProvider.prototype
* @type {Boolean}
* @readonly
* @default true
*/
hasAlphaChannel : {
get : function() {
if (!this.ready) {
throw new DeveloperError('hasAlphaChannel must not be called before the imagery provider is ready.');
}
return this._hasAlphaChannel;
}
}
});
/**
* Reinitializes this instance. Reinitializing an instance already in use is supported, but it is not
* recommended because existing tiles provided by the imagery provider will not be updated.
*
* @param {Promise.|Object} options Any of the options that may be passed to the {@link UrlTemplateImageryProvider} constructor.
*/
UrlTemplateImageryProvider.prototype.reinitialize = function(options) {
var that = this;
that._readyPromise = when(options).then(function(properties) {
if (!defined(properties)) {
throw new DeveloperError('options is required.');
}
if (!defined(properties.url)) {
throw new DeveloperError('options.url is required.');
}
that.enablePickFeatures = defaultValue(properties.enablePickFeatures, that.enablePickFeatures);
that._url = properties.url;
that._urlSchemeZeroPadding = defaultValue(properties.urlSchemeZeroPadding, that.urlSchemeZeroPadding);
that._pickFeaturesUrl = properties.pickFeaturesUrl;
that._proxy = properties.proxy;
that._tileDiscardPolicy = properties.tileDiscardPolicy;
that._getFeatureInfoFormats = properties.getFeatureInfoFormats;
that._subdomains = properties.subdomains;
if (isArray(that._subdomains)) {
that._subdomains = that._subdomains.slice();
} else if (defined(that._subdomains) && that._subdomains.length > 0) {
that._subdomains = that._subdomains.split('');
} else {
that._subdomains = ['a', 'b', 'c'];
}
that._tileWidth = defaultValue(properties.tileWidth, 256);
that._tileHeight = defaultValue(properties.tileHeight, 256);
that._minimumLevel = defaultValue(properties.minimumLevel, 0);
that._maximumLevel = properties.maximumLevel;
that._tilingScheme = defaultValue(properties.tilingScheme, new WebMercatorTilingScheme({ ellipsoid : properties.ellipsoid }));
that._rectangle = defaultValue(properties.rectangle, that._tilingScheme.rectangle);
that._rectangle = Rectangle.intersection(that._rectangle, that._tilingScheme.rectangle);
that._hasAlphaChannel = defaultValue(properties.hasAlphaChannel, true);
var credit = properties.credit;
if (typeof credit === 'string') {
credit = new Credit(credit);
}
that._credit = credit;
that._urlParts = urlTemplateToParts(that._url, tags);
that._pickFeaturesUrlParts = urlTemplateToParts(that._pickFeaturesUrl, pickFeaturesTags);
return true;
});
};
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
UrlTemplateImageryProvider.prototype.getTileCredits = function(x, y, level) {
if (!this.ready) {
throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.');
}
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link UrlTemplateImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*/
UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level) {
if (!this.ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
}
var url = buildImageUrl(this, x, y, level);
return ImageryProvider.loadImage(this, url);
};
/**
* Asynchronously determines what features, if any, are located at a given longitude and latitude within
* a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
UrlTemplateImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
if (!this.ready) {
throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.');
}
if (!this.enablePickFeatures || !defined(this._pickFeaturesUrl) || this._getFeatureInfoFormats.length === 0) {
return undefined;
}
var formatIndex = 0;
var that = this;
function handleResponse(format, data) {
return format.callback(data);
}
function doRequest() {
if (formatIndex >= that._getFeatureInfoFormats.length) {
// No valid formats, so no features picked.
return when([]);
}
var format = that._getFeatureInfoFormats[formatIndex];
var url = buildPickFeaturesUrl(that, x, y, level, longitude, latitude, format.format);
++formatIndex;
if (format.type === 'json') {
return loadJson(url).then(format.callback).otherwise(doRequest);
} else if (format.type === 'xml') {
return loadXML(url).then(format.callback).otherwise(doRequest);
} else if (format.type === 'text' || format.type === 'html') {
return loadText(url).then(format.callback).otherwise(doRequest);
} else {
return loadWithXhr({
url: url,
responseType: format.format
}).then(handleResponse.bind(undefined, format)).otherwise(doRequest);
}
}
return doRequest();
};
function buildImageUrl(imageryProvider, x, y, level) {
degreesScratchComputed = false;
projectedScratchComputed = false;
return buildUrl(imageryProvider, imageryProvider._urlParts, function(partFunction) {
return partFunction(imageryProvider, x, y, level);
});
}
function buildPickFeaturesUrl(imageryProvider, x, y, level, longitude, latitude, format) {
degreesScratchComputed = false;
projectedScratchComputed = false;
ijScratchComputed = false;
longitudeLatitudeProjectedScratchComputed = false;
return buildUrl(imageryProvider, imageryProvider._pickFeaturesUrlParts, function(partFunction) {
return partFunction(imageryProvider, x, y, level, longitude, latitude, format);
});
}
function buildUrl(imageryProvider, parts, partFunctionInvoker) {
var url = '';
for (var i = 0; i < parts.length; ++i) {
var part = parts[i];
if (typeof part === 'string') {
url += part;
} else {
url += encodeURIComponent(partFunctionInvoker(part));
}
}
var proxy = imageryProvider._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
return url;
}
function urlTemplateToParts(url, tags) {
if (!defined(url)) {
return undefined;
}
var parts = [];
var nextIndex = 0;
var minIndex;
var minTag;
var tagList = Object.keys(tags);
while (nextIndex < url.length) {
minIndex = Number.MAX_VALUE;
minTag = undefined;
for (var i = 0; i < tagList.length; ++i) {
var thisIndex = url.indexOf(tagList[i], nextIndex);
if (thisIndex >= 0 && thisIndex < minIndex) {
minIndex = thisIndex;
minTag = tagList[i];
}
}
if (!defined(minTag)) {
parts.push(url.substring(nextIndex));
nextIndex = url.length;
} else {
if (nextIndex < minIndex) {
parts.push(url.substring(nextIndex, minIndex));
}
parts.push(tags[minTag]);
nextIndex = minIndex + minTag.length;
}
}
return parts;
}
function padWithZerosIfNecessary(imageryProvider, key, value) {
if (imageryProvider &&
imageryProvider.urlSchemeZeroPadding &&
imageryProvider.urlSchemeZeroPadding.hasOwnProperty(key) )
{
var paddingTemplate = imageryProvider.urlSchemeZeroPadding[key];
if (typeof paddingTemplate === 'string') {
var paddingTemplateWidth = paddingTemplate.length;
if (paddingTemplateWidth > 1) {
value = (value.length >= paddingTemplateWidth) ? value : new Array(paddingTemplateWidth - value.toString().length + 1).join('0') + value;
}
}
}
return value;
}
function xTag(imageryProvider, x, y, level) {
return padWithZerosIfNecessary(imageryProvider, '{x}', x);
}
function reverseXTag(imageryProvider, x, y, level) {
var reverseX = imageryProvider.tilingScheme.getNumberOfXTilesAtLevel(level) - x - 1;
return padWithZerosIfNecessary(imageryProvider, '{reverseX}', reverseX);
}
function yTag(imageryProvider, x, y, level) {
return padWithZerosIfNecessary(imageryProvider, '{y}', y);
}
function reverseYTag(imageryProvider, x, y, level) {
var reverseY = imageryProvider.tilingScheme.getNumberOfYTilesAtLevel(level) - y - 1;
return padWithZerosIfNecessary(imageryProvider, '{reverseY}', reverseY);
}
function reverseZTag(imageryProvider, x, y, level) {
var maximumLevel = imageryProvider.maximumLevel;
var reverseZ = defined(maximumLevel) && level < maximumLevel ? maximumLevel - level - 1 : level;
return padWithZerosIfNecessary(imageryProvider, '{reverseZ}', reverseZ);
}
function zTag(imageryProvider, x, y, level) {
return padWithZerosIfNecessary(imageryProvider, '{z}', level);
}
function sTag(imageryProvider, x, y, level) {
var index = (x + y + level) % imageryProvider._subdomains.length;
return imageryProvider._subdomains[index];
}
var degreesScratchComputed = false;
var degreesScratch = new Rectangle();
function computeDegrees(imageryProvider, x, y, level) {
if (degreesScratchComputed) {
return;
}
imageryProvider.tilingScheme.tileXYToRectangle(x, y, level, degreesScratch);
degreesScratch.west = CesiumMath.toDegrees(degreesScratch.west);
degreesScratch.south = CesiumMath.toDegrees(degreesScratch.south);
degreesScratch.east = CesiumMath.toDegrees(degreesScratch.east);
degreesScratch.north = CesiumMath.toDegrees(degreesScratch.north);
degreesScratchComputed = true;
}
function westDegreesTag(imageryProvider, x, y, level) {
computeDegrees(imageryProvider, x, y, level);
return degreesScratch.west;
}
function southDegreesTag(imageryProvider, x, y, level) {
computeDegrees(imageryProvider, x, y, level);
return degreesScratch.south;
}
function eastDegreesTag(imageryProvider, x, y, level) {
computeDegrees(imageryProvider, x, y, level);
return degreesScratch.east;
}
function northDegreesTag(imageryProvider, x, y, level) {
computeDegrees(imageryProvider, x, y, level);
return degreesScratch.north;
}
var projectedScratchComputed = false;
var projectedScratch = new Rectangle();
function computeProjected(imageryProvider, x, y, level) {
if (projectedScratchComputed) {
return;
}
imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, projectedScratch);
projectedScratchComputed = true;
}
function westProjectedTag(imageryProvider, x, y, level) {
computeProjected(imageryProvider, x, y, level);
return projectedScratch.west;
}
function southProjectedTag(imageryProvider, x, y, level) {
computeProjected(imageryProvider, x, y, level);
return projectedScratch.south;
}
function eastProjectedTag(imageryProvider, x, y, level) {
computeProjected(imageryProvider, x, y, level);
return projectedScratch.east;
}
function northProjectedTag(imageryProvider, x, y, level) {
computeProjected(imageryProvider, x, y, level);
return projectedScratch.north;
}
function widthTag(imageryProvider, x, y, level) {
return imageryProvider.tileWidth;
}
function heightTag(imageryProvider, x, y, level) {
return imageryProvider.tileHeight;
}
var ijScratchComputed = false;
var ijScratch = new Cartesian2();
function iTag(imageryProvider, x, y, level, longitude, latitude, format) {
computeIJ(imageryProvider, x, y, level, longitude, latitude);
return ijScratch.x;
}
function jTag(imageryProvider, x, y, level, longitude, latitude, format) {
computeIJ(imageryProvider, x, y, level, longitude, latitude);
return ijScratch.y;
}
function reverseITag(imageryProvider, x, y, level, longitude, latitude, format) {
computeIJ(imageryProvider, x, y, level, longitude, latitude);
return imageryProvider.tileWidth - ijScratch.x - 1;
}
function reverseJTag(imageryProvider, x, y, level, longitude, latitude, format) {
computeIJ(imageryProvider, x, y, level, longitude, latitude);
return imageryProvider.tileHeight - ijScratch.y - 1;
}
var rectangleScratch = new Rectangle();
function computeIJ(imageryProvider, x, y, level, longitude, latitude, format) {
if (ijScratchComputed) {
return;
}
computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
var projected = longitudeLatitudeProjectedScratch;
var rectangle = imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, rectangleScratch);
ijScratch.x = (imageryProvider.tileWidth * (projected.x - rectangle.west) / rectangle.width) | 0;
ijScratch.y = (imageryProvider.tileHeight * (rectangle.north - projected.y) / rectangle.height) | 0;
ijScratchComputed = true;
}
function longitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
return CesiumMath.toDegrees(longitude);
}
function latitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
return CesiumMath.toDegrees(latitude);
}
var longitudeLatitudeProjectedScratchComputed = false;
var longitudeLatitudeProjectedScratch = new Cartesian3();
function longitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
return longitudeLatitudeProjectedScratch.x;
}
function latitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
return longitudeLatitudeProjectedScratch.y;
}
var cartographicScratch = new Cartographic();
function computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude, format) {
if (longitudeLatitudeProjectedScratchComputed) {
return;
}
var projected;
if (imageryProvider.tilingScheme instanceof GeographicTilingScheme) {
longitudeLatitudeProjectedScratch.x = CesiumMath.toDegrees(longitude);
longitudeLatitudeProjectedScratch.y = CesiumMath.toDegrees(latitude);
} else {
var cartographic = cartographicScratch;
cartographic.longitude = longitude;
cartographic.latitude = latitude;
projected = imageryProvider.tilingScheme.projection.project(cartographic, longitudeLatitudeProjectedScratch);
}
longitudeLatitudeProjectedScratchComputed = true;
}
function formatTag(imageryProvider, x, y, level, longitude, latitude, format) {
return format;
}
var tags = {
'{x}': xTag,
'{y}': yTag,
'{z}': zTag,
'{s}': sTag,
'{reverseX}': reverseXTag,
'{reverseY}': reverseYTag,
'{reverseZ}': reverseZTag,
'{westDegrees}': westDegreesTag,
'{southDegrees}': southDegreesTag,
'{eastDegrees}': eastDegreesTag,
'{northDegrees}': northDegreesTag,
'{westProjected}': westProjectedTag,
'{southProjected}': southProjectedTag,
'{eastProjected}': eastProjectedTag,
'{northProjected}': northProjectedTag,
'{width}': widthTag,
'{height}': heightTag
};
var pickFeaturesTags = combine(tags, {
'{i}' : iTag,
'{j}' : jTag,
'{reverseI}' : reverseITag,
'{reverseJ}' : reverseJTag,
'{longitudeDegrees}' : longitudeDegreesTag,
'{latitudeDegrees}' : latitudeDegreesTag,
'{longitudeProjected}' : longitudeProjectedTag,
'{latitudeProjected}' : latitudeProjectedTag,
'{format}' : formatTag
});
return UrlTemplateImageryProvider;
});
/*global define*/
define('Scene/createOpenStreetMapImageryProvider',[
'../Core/Credit',
'../Core/defaultValue',
'../Core/DeveloperError',
'../Core/Rectangle',
'../Core/WebMercatorTilingScheme',
'./UrlTemplateImageryProvider'
], function(
Credit,
defaultValue,
DeveloperError,
Rectangle,
WebMercatorTilingScheme,
UrlTemplateImageryProvider) {
'use strict';
var trailingSlashRegex = /\/$/;
var defaultCredit = new Credit('MapQuest, Open Street Map and contributors, CC-BY-SA');
/**
* Creates a {@link UrlTemplateImageryProvider} instance that provides tiled imagery hosted by OpenStreetMap
* or another provider of Slippy tiles. The default url connects to OpenStreetMap's volunteer-run
* servers, so you must conform to their
* {@link http://wiki.openstreetmap.org/wiki/Tile_usage_policy|Tile Usage Policy}.
*
* @exports createOpenStreetMapImageryProvider
*
* @param {Object} [options] Object with the following properties:
* @param {String} [options.url='https://a.tile.openstreetmap.org'] The OpenStreetMap server url.
* @param {String} [options.fileExtension='png'] The file extension for images on the server.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer.
* @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.
* @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {Credit|String} [options.credit='MapQuest, Open Street Map and contributors, CC-BY-SA'] A credit for the data source, which is displayed on the canvas.
* @returns {UrlTemplateImageryProvider} The imagery provider.
*
* @exception {DeveloperError} The rectangle and minimumLevel indicate that there are more than four tiles at the minimum level. Imagery providers with more than four tiles at the minimum level are not supported.
*
* @see ArcGisMapServerImageryProvider
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
* @see UrlTemplateImageryProvider
*
*
* @example
* var osm = Cesium.createOpenStreetMapImageryProvider({
* url : 'https://a.tile.openstreetmap.org/'
* });
*
* @see {@link http://wiki.openstreetmap.org/wiki/Main_Page|OpenStreetMap Wiki}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
*/
function createOpenStreetMapImageryProvider(options) {
options = defaultValue(options, {});
var url = defaultValue(options.url, 'https://a.tile.openstreetmap.org/');
if (!trailingSlashRegex.test(url)) {
url = url + '/';
}
var fileExtension = defaultValue(options.fileExtension, 'png');
var tilingScheme = new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
var tileWidth = 256;
var tileHeight = 256;
var minimumLevel = defaultValue(options.minimumLevel, 0);
var maximumLevel = options.maximumLevel;
var rectangle = defaultValue(options.rectangle, tilingScheme.rectangle);
// Check the number of tiles at the minimum level. If it's more than four,
// throw an exception, because starting at the higher minimum
// level will cause too many tiles to be downloaded and rendered.
var swTile = tilingScheme.positionToTileXY(Rectangle.southwest(rectangle), minimumLevel);
var neTile = tilingScheme.positionToTileXY(Rectangle.northeast(rectangle), minimumLevel);
var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
if (tileCount > 4) {
throw new DeveloperError('The rectangle and minimumLevel indicate that there are ' + tileCount + ' tiles at the minimum level. Imagery providers with more than four tiles at the minimum level are not supported.');
}
var credit = defaultValue(options.credit, defaultCredit);
if (typeof credit === 'string') {
credit = new Credit(credit);
}
var templateUrl = url + "{z}/{x}/{y}." + fileExtension;
return new UrlTemplateImageryProvider({
url: templateUrl,
proxy: options.proxy,
credit: credit,
tilingScheme: tilingScheme,
tileWidth: tileWidth,
tileHeight: tileHeight,
minimumLevel: minimumLevel,
maximumLevel: maximumLevel,
rectangle: rectangle
});
}
return createOpenStreetMapImageryProvider;
});
/*global define*/
define('Scene/createTangentSpaceDebugPrimitive',[
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/GeometryInstance',
'../Core/GeometryPipeline',
'../Core/Matrix4',
'./PerInstanceColorAppearance',
'./Primitive'
], function(
ColorGeometryInstanceAttribute,
defaultValue,
defined,
DeveloperError,
GeometryInstance,
GeometryPipeline,
Matrix4,
PerInstanceColorAppearance,
Primitive) {
'use strict';
/**
* Creates a {@link Primitive} to visualize well-known vector vertex attributes:
* normal
, binormal
, and tangent
. Normal
* is red; binormal is green; and tangent is blue. If an attribute is not
* present, it is not drawn.
*
* @exports createTangentSpaceDebugPrimitive
*
* @param {Object} options Object with the following properties:
* @param {Geometry} options.geometry The Geometry
instance with the attribute.
* @param {Number} [options.length=10000.0] The length of each line segment in meters. This can be negative to point the vector in the opposite direction.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix that transforms to transform the geometry from model to world coordinates.
* @returns {Primitive} A new Primitive
instance with geometry for the vectors.
*
* @example
* scene.primitives.add(Cesium.createTangentSpaceDebugPrimitive({
* geometry : instance.geometry,
* length : 100000.0,
* modelMatrix : instance.modelMatrix
* }));
*/
function createTangentSpaceDebugPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var instances = [];
var geometry = options.geometry;
if (!defined(geometry)) {
throw new DeveloperError('options.geometry is required.');
}
if (!defined(geometry.attributes) || !defined(geometry.primitiveType)) {
// to create the debug lines, we need the computed attributes.
// compute them if they are undefined.
geometry = geometry.constructor.createGeometry(geometry);
}
var attributes = geometry.attributes;
var modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
var length = defaultValue(options.length, 10000.0);
if (defined(attributes.normal)) {
instances.push(new GeometryInstance({
geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'normal', length),
attributes : {
color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
},
modelMatrix : modelMatrix
}));
}
if (defined(attributes.binormal)) {
instances.push(new GeometryInstance({
geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'binormal', length),
attributes : {
color : new ColorGeometryInstanceAttribute(0.0, 1.0, 0.0, 1.0)
},
modelMatrix : modelMatrix
}));
}
if (defined(attributes.tangent)) {
instances.push(new GeometryInstance({
geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'tangent', length),
attributes : {
color : new ColorGeometryInstanceAttribute(0.0, 0.0, 1.0, 1.0)
},
modelMatrix : modelMatrix
}));
}
if (instances.length > 0) {
return new Primitive({
asynchronous : false,
geometryInstances : instances,
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : false
})
});
}
return undefined;
}
return createTangentSpaceDebugPrimitive;
});
/*global define*/
define('Scene/createTileMapServiceImageryProvider',[
'../Core/Cartesian2',
'../Core/Cartographic',
'../Core/defaultValue',
'../Core/defined',
'../Core/DeveloperError',
'../Core/GeographicTilingScheme',
'../Core/joinUrls',
'../Core/loadXML',
'../Core/Rectangle',
'../Core/RuntimeError',
'../Core/TileProviderError',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/when',
'./UrlTemplateImageryProvider'
], function(
Cartesian2,
Cartographic,
defaultValue,
defined,
DeveloperError,
GeographicTilingScheme,
joinUrls,
loadXML,
Rectangle,
RuntimeError,
TileProviderError,
WebMercatorTilingScheme,
when,
UrlTemplateImageryProvider) {
'use strict';
/**
* Creates a {@link UrlTemplateImageryProvider} instance that provides tiled imagery as generated by
* {@link http://www.maptiler.org/'>MapTiler / tilingScheme.rectangle.east) {
rectangle.east = tilingScheme.rectangle.east;
}
if (rectangle.south < tilingScheme.rectangle.south) {
rectangle.south = tilingScheme.rectangle.south;
}
if (rectangle.north > tilingScheme.rectangle.north) {
rectangle.north = tilingScheme.rectangle.north;
}
// Check the number of tiles at the minimum level. If it's more than four,
// try requesting the lower levels anyway, because starting at the higher minimum
// level will cause too many tiles to be downloaded and rendered.
var swTile = tilingScheme.positionToTileXY(Rectangle.southwest(rectangle), minimumLevel);
var neTile = tilingScheme.positionToTileXY(Rectangle.northeast(rectangle), minimumLevel);
var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
if (tileCount > 4) {
minimumLevel = 0;
}
var templateUrl = joinUrls(url, '{z}/{x}/{reverseY}.' + fileExtension);
deferred.resolve({
url : templateUrl,
tilingScheme : tilingScheme,
rectangle : rectangle,
tileWidth : tileWidth,
tileHeight : tileHeight,
minimumLevel : minimumLevel,
maximumLevel : maximumLevel,
proxy : options.proxy,
tileDiscardPolicy : options.tileDiscardPolicy,
credit: options.credit
});
}
function metadataFailure(error) {
// Can't load XML, still allow options and defaults
var fileExtension = defaultValue(options.fileExtension, 'png');
var tileWidth = defaultValue(options.tileWidth, 256);
var tileHeight = defaultValue(options.tileHeight, 256);
var minimumLevel = defaultValue(options.minimumLevel, 0);
var maximumLevel = options.maximumLevel;
var tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
var rectangle = defaultValue(options.rectangle, tilingScheme.rectangle);
var templateUrl = joinUrls(url, '{z}/{x}/{reverseY}.' + fileExtension);
deferred.resolve({
url : templateUrl,
tilingScheme : tilingScheme,
rectangle : rectangle,
tileWidth : tileWidth,
tileHeight : tileHeight,
minimumLevel : minimumLevel,
maximumLevel : maximumLevel,
proxy : options.proxy,
tileDiscardPolicy : options.tileDiscardPolicy,
credit: options.credit
});
}
function requestMetadata() {
var resourceUrl = joinUrls(url, 'tilemapresource.xml');
var proxy = options.proxy;
if (defined(proxy)) {
resourceUrl = proxy.getURL(resourceUrl);
}
// Try to load remaining parameters from XML
loadXML(resourceUrl).then(metadataSuccess).otherwise(metadataFailure);
}
requestMetadata();
return imageryProvider;
}
return createTileMapServiceImageryProvider;
});
/*global define*/
define('Scene/CreditDisplay',[
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError'
], function(
Credit,
defaultValue,
defined,
destroyObject,
DeveloperError) {
'use strict';
function displayTextCredit(credit, container, delimiter) {
if (!defined(credit.element)) {
var text = credit.text;
var link = credit.link;
var span = document.createElement('span');
if (credit.hasLink()) {
var a = document.createElement('a');
a.textContent = text;
a.href = link;
a.target = '_blank';
span.appendChild(a);
} else {
span.textContent = text;
}
span.className = 'cesium-credit-text';
credit.element = span;
}
if (container.hasChildNodes()) {
var del = document.createElement('span');
del.textContent = delimiter;
del.className = 'cesium-credit-delimiter';
container.appendChild(del);
}
container.appendChild(credit.element);
}
function displayImageCredit(credit, container) {
if (!defined(credit.element)) {
var text = credit.text;
var link = credit.link;
var span = document.createElement('span');
var content = document.createElement('img');
content.src = credit.imageUrl;
content.style['vertical-align'] = 'bottom';
if (defined(text)) {
content.alt = text;
content.title = text;
}
if (credit.hasLink()) {
var a = document.createElement('a');
a.appendChild(content);
a.href = link;
a.target = '_blank';
span.appendChild(a);
} else {
span.appendChild(content);
}
span.className = 'cesium-credit-image';
credit.element = span;
}
container.appendChild(credit.element);
}
function contains(credits, credit) {
var len = credits.length;
for ( var i = 0; i < len; i++) {
var existingCredit = credits[i];
if (Credit.equals(existingCredit, credit)) {
return true;
}
}
return false;
}
function removeCreditDomElement(credit) {
var element = credit.element;
if (defined(element)) {
var container = element.parentNode;
if (!credit.hasImage()) {
var delimiter = element.previousSibling;
if (delimiter === null) {
delimiter = element.nextSibling;
}
if (delimiter !== null) {
container.removeChild(delimiter);
}
}
container.removeChild(element);
}
}
function displayTextCredits(creditDisplay, textCredits) {
var i;
var index;
var credit;
var displayedTextCredits = creditDisplay._displayedCredits.textCredits;
for (i = 0; i < textCredits.length; i++) {
credit = textCredits[i];
if (defined(credit)) {
index = displayedTextCredits.indexOf(credit);
if (index === -1) {
displayTextCredit(credit, creditDisplay._textContainer, creditDisplay._delimiter);
} else {
displayedTextCredits.splice(index, 1);
}
}
}
for (i = 0; i < displayedTextCredits.length; i++) {
credit = displayedTextCredits[i];
if (defined(credit)) {
removeCreditDomElement(credit);
}
}
}
function displayImageCredits(creditDisplay, imageCredits) {
var i;
var index;
var credit;
var displayedImageCredits = creditDisplay._displayedCredits.imageCredits;
for (i = 0; i < imageCredits.length; i++) {
credit = imageCredits[i];
if (defined(credit)) {
index = displayedImageCredits.indexOf(credit);
if (index === -1) {
displayImageCredit(credit, creditDisplay._imageContainer);
} else {
displayedImageCredits.splice(index, 1);
}
}
}
for (i = 0; i < displayedImageCredits.length; i++) {
credit = displayedImageCredits[i];
if (defined(credit)) {
removeCreditDomElement(credit);
}
}
}
/**
* The credit display is responsible for displaying credits on screen.
*
* @param {HTMLElement} container The HTML element where credits will be displayed
* @param {String} [delimiter= ' • '] The string to separate text credits
*
* @alias CreditDisplay
* @constructor
*
* @example
* var creditDisplay = new Cesium.CreditDisplay(creditContainer);
*/
function CreditDisplay(container, delimiter) {
if (!defined(container)) {
throw new DeveloperError('credit container is required');
}
var imageContainer = document.createElement('span');
imageContainer.className = 'cesium-credit-imageContainer';
var textContainer = document.createElement('span');
textContainer.className = 'cesium-credit-textContainer';
container.appendChild(imageContainer);
container.appendChild(textContainer);
this._delimiter = defaultValue(delimiter, ' • ');
this._textContainer = textContainer;
this._imageContainer = imageContainer;
this._defaultImageCredits = [];
this._defaultTextCredits = [];
this._displayedCredits = {
imageCredits : [],
textCredits : []
};
this._currentFrameCredits = {
imageCredits : [],
textCredits : []
};
/**
* The HTML element where credits will be displayed.
* @type {HTMLElement}
*/
this.container = container;
}
/**
* Adds a credit to the list of current credits to be displayed in the credit container
*
* @param {Credit} credit The credit to display
*/
CreditDisplay.prototype.addCredit = function(credit) {
if (!defined(credit)) {
throw new DeveloperError('credit must be defined');
}
if (credit.hasImage()) {
var imageCredits = this._currentFrameCredits.imageCredits;
if (!contains(this._defaultImageCredits, credit)) {
imageCredits[credit.id] = credit;
}
} else {
var textCredits = this._currentFrameCredits.textCredits;
if (!contains(this._defaultTextCredits, credit)) {
textCredits[credit.id] = credit;
}
}
};
/**
* Adds credits that will persist until they are removed
*
* @param {Credit} credit The credit to added to defaults
*/
CreditDisplay.prototype.addDefaultCredit = function(credit) {
if (!defined(credit)) {
throw new DeveloperError('credit must be defined');
}
if (credit.hasImage()) {
var imageCredits = this._defaultImageCredits;
if (!contains(imageCredits, credit)) {
imageCredits.push(credit);
}
} else {
var textCredits = this._defaultTextCredits;
if (!contains(textCredits, credit)) {
textCredits.push(credit);
}
}
};
/**
* Removes a default credit
*
* @param {Credit} credit The credit to be removed from defaults
*/
CreditDisplay.prototype.removeDefaultCredit = function(credit) {
if (!defined(credit)) {
throw new DeveloperError('credit must be defined');
}
var index;
if (credit.hasImage()) {
index = this._defaultImageCredits.indexOf(credit);
if (index !== -1) {
this._defaultImageCredits.splice(index, 1);
}
} else {
index = this._defaultTextCredits.indexOf(credit);
if (index !== -1) {
this._defaultTextCredits.splice(index, 1);
}
}
};
/**
* Resets the credit display to a beginning of frame state, clearing out current credits.
*
* @param {Credit} credit The credit to display
*/
CreditDisplay.prototype.beginFrame = function() {
this._currentFrameCredits.imageCredits.length = 0;
this._currentFrameCredits.textCredits.length = 0;
};
/**
* Sets the credit display to the end of frame state, displaying current credits in the credit container
*
* @param {Credit} credit The credit to display
*/
CreditDisplay.prototype.endFrame = function() {
var textCredits = this._defaultTextCredits.concat(this._currentFrameCredits.textCredits);
var imageCredits = this._defaultImageCredits.concat(this._currentFrameCredits.imageCredits);
displayTextCredits(this, textCredits);
displayImageCredits(this, imageCredits);
this._displayedCredits.textCredits = textCredits;
this._displayedCredits.imageCredits = imageCredits;
};
/**
* Destroys the resources held by this object. Destroying an object allows for deterministic
* release of resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
CreditDisplay.prototype.destroy = function() {
this.container.removeChild(this._textContainer);
this.container.removeChild(this._imageContainer);
return destroyObject(this);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*/
CreditDisplay.prototype.isDestroyed = function() {
return false;
};
return CreditDisplay;
});
/*global define*/
define('Scene/DebugAppearance',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'./Appearance'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Appearance) {
'use strict';
/**
* Visualizes a vertex attribute by displaying it as a color for debugging.
*
* Components for well-known unit-length vectors, i.e., normal
,
* binormal
, and tangent
, are scaled and biased
* from [-1.0, 1.0] to (-1.0, 1.0).
*
*
* @alias DebugAppearance
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.attributeName The name of the attribute to visualize.
* @param {Boolean} options.perInstanceAttribute Boolean that determines whether this attribute is a per-instance geometry attribute.
* @param {String} [options.glslDatatype='vec3'] The GLSL datatype of the attribute. Supported datatypes are float
, vec2
, vec3
, and vec4
.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @exception {DeveloperError} options.glslDatatype must be float, vec2, vec3, or vec4.
*
* @example
* var primitive = new Cesium.Primitive({
* geometryInstances : // ...
* appearance : new Cesium.DebugAppearance({
* attributeName : 'normal'
* })
* });
*/
function DebugAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var attributeName = options.attributeName;
var perInstanceAttribute = options.perInstanceAttribute;
if (!defined(attributeName)) {
throw new DeveloperError('options.attributeName is required.');
}
if (!defined(perInstanceAttribute)) {
throw new DeveloperError('options.perInstanceAttribute is required.');
}
var glslDatatype = defaultValue(options.glslDatatype, 'vec3');
var varyingName = 'v_' + attributeName;
var getColor;
// Well-known normalized vector attributes in VertexFormat
if ((attributeName === 'normal') || (attributeName === 'binormal') || (attributeName === 'tangent')) {
getColor = 'vec4 getColor() { return vec4((' + varyingName + ' + vec3(1.0)) * 0.5, 1.0); }\n';
} else {
// All other attributes, both well-known and custom
if (attributeName === 'st') {
glslDatatype = 'vec2';
}
switch(glslDatatype) {
case 'float':
getColor = 'vec4 getColor() { return vec4(vec3(' + varyingName + '), 1.0); }\n';
break;
case 'vec2':
getColor = 'vec4 getColor() { return vec4(' + varyingName + ', 0.0, 1.0); }\n';
break;
case 'vec3':
getColor = 'vec4 getColor() { return vec4(' + varyingName + ', 1.0); }\n';
break;
case 'vec4':
getColor = 'vec4 getColor() { return ' + varyingName + '; }\n';
break;
default:
throw new DeveloperError('options.glslDatatype must be float, vec2, vec3, or vec4.');
}
}
var vs =
'attribute vec3 position3DHigh;\n' +
'attribute vec3 position3DLow;\n' +
'attribute float batchId;\n' +
(perInstanceAttribute ? '' : 'attribute ' + glslDatatype + ' ' + attributeName + ';\n') +
'varying ' + glslDatatype + ' ' + varyingName + ';\n' +
'void main()\n' +
'{\n' +
'vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n' +
(perInstanceAttribute ?
varyingName + ' = czm_batchTable_' + attributeName + '(batchId);\n' :
varyingName + ' = ' + attributeName + ';\n') +
'gl_Position = czm_modelViewProjectionRelativeToEye * p;\n' +
'}';
var fs =
'varying ' + glslDatatype + ' ' + varyingName + ';\n' +
getColor + '\n' +
'void main()\n' +
'{\n' +
'gl_FragColor = getColor();\n' +
'}';
/**
* This property is part of the {@link Appearance} interface, but is not
* used by {@link DebugAppearance} since a fully custom fragment shader is used.
*
* @type Material
*
* @default undefined
*/
this.material = undefined;
/**
* When true
, the geometry is expected to appear translucent.
*
* @type {Boolean}
*
* @default false
*/
this.translucent = defaultValue(options.translucent, false);
this._vertexShaderSource = defaultValue(options.vertexShaderSource, vs);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs);
this._renderState = Appearance.getDefaultRenderState(false, false, options.renderState);
this._closed = defaultValue(options.closed, false);
// Non-derived members
this._attributeName = attributeName;
this._glslDatatype = glslDatatype;
}
defineProperties(DebugAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof DebugAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader. The full fragment shader
* source is built procedurally taking into account the {@link DebugAppearance#material}.
* Use {@link DebugAppearance#getFragmentShaderSource} to get the full source.
*
* @memberof DebugAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* @memberof DebugAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed.
*
* @memberof DebugAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
},
/**
* The name of the attribute being visualized.
*
* @memberof DebugAppearance.prototype
*
* @type {String}
* @readonly
*/
attributeName : {
get : function() {
return this._attributeName;
}
},
/**
* The GLSL datatype of the attribute being visualized.
*
* @memberof DebugAppearance.prototype
*
* @type {String}
* @readonly
*/
glslDatatype : {
get : function() {
return this._glslDatatype;
}
}
});
/**
* Returns the full GLSL fragment shader source, which for {@link DebugAppearance} is just
* {@link DebugAppearance#fragmentShaderSource}.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
DebugAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link DebugAppearance#translucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
DebugAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
DebugAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
return DebugAppearance;
});
/*global define*/
define('Scene/DebugCameraPrimitive',[
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/GeometryAttribute',
'../Core/GeometryAttributes',
'../Core/GeometryInstance',
'../Core/Matrix4',
'../Core/PrimitiveType',
'./PerInstanceColorAppearance',
'./Primitive'
], function(
BoundingSphere,
Cartesian3,
Cartesian4,
Color,
ColorGeometryInstanceAttribute,
ComponentDatatype,
defaultValue,
defined,
destroyObject,
DeveloperError,
GeometryAttribute,
GeometryAttributes,
GeometryInstance,
Matrix4,
PrimitiveType,
PerInstanceColorAppearance,
Primitive) {
'use strict';
/**
* Draws the outline of the camera's view frustum.
*
* @alias DebugCameraPrimitive
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Camera} options.camera The camera.
* @param {Color} [options.color=Color.CYAN] The color of the debug outline.
* @param {Boolean} [options.updateOnChange=true] Whether the primitive updates when the underlying camera changes.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
* @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}.
*
* @example
* primitives.add(new Cesium.DebugCameraPrimitive({
* camera : camera,
* color : Cesium.Color.YELLOW
* }));
*/
function DebugCameraPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.camera)) {
throw new DeveloperError('options.camera is required.');
}
this._camera = options.camera;
this._color = defaultValue(options.color, Color.CYAN);
this._updateOnChange = defaultValue(options.updateOnChange, true);
/**
* Determines if this primitive will be shown.
*
* @type Boolean
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* User-defined object returned when the primitive is picked.
*
* @type {Object}
* @default undefined
*
* @see Scene#pick
*/
this.id = options.id;
this._id = undefined;
this._outlinePrimitive = undefined;
this._planesPrimitive = undefined;
}
var frustumCornersNDC = new Array(8);
frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0);
frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0);
frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0);
frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0);
frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);
var scratchMatrix = new Matrix4();
var scratchFrustumCorners = new Array(8);
for (var i = 0; i < 8; ++i) {
scratchFrustumCorners[i] = new Cartesian4();
}
var scratchColor = new Color();
/**
* @private
*/
DebugCameraPrimitive.prototype.update = function(frameState) {
if (!this.show) {
return;
}
if (this._updateOnChange) {
// Recreate the primitive every frame
this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy();
this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy();
}
if (!defined(this._outlinePrimitive)) {
var view = this._camera.viewMatrix;
var projection = this._camera.frustum.projectionMatrix;
var viewProjection = Matrix4.multiply(projection, view, scratchMatrix);
var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
var positions = new Float64Array(8 * 3);
for (var i = 0; i < 8; ++i) {
var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);
Matrix4.multiplyByVector(inverseViewProjection, corner, corner);
Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
positions[i * 3] = corner.x;
positions[i * 3 + 1] = corner.y;
positions[i * 3 + 2] = corner.z;
}
var boundingSphere = new BoundingSphere.fromVertices(positions);
var attributes = new GeometryAttributes();
attributes.position = new GeometryAttribute({
componentDatatype : ComponentDatatype.DOUBLE,
componentsPerAttribute : 3,
values : positions
});
// Create the outline primitive
var outlineIndices = new Uint16Array([0,1,1,2,2,3,3,0,0,4,4,7,7,3,7,6,6,2,2,1,1,5,5,4,5,6]);
this._outlinePrimitive = new Primitive({
geometryInstances : new GeometryInstance({
geometry : {
attributes : attributes,
indices : outlineIndices,
primitiveType : PrimitiveType.LINES,
boundingSphere : boundingSphere
},
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(this._color)
},
id : this.id,
pickPrimitive : this
}),
appearance : new PerInstanceColorAppearance({
translucent : false,
flat : true
}),
asynchronous : false
});
// Create the planes primitive
var planesIndices = new Uint16Array([4,5,6,4,6,7,5,1,2,5,2,6,7,6,2,7,2,3,0,1,5,0,5,4,0,4,7,0,7,3,1,0,3,1,3,2]);
this._planesPrimitive = new Primitive({
geometryInstances : new GeometryInstance({
geometry : {
attributes : attributes,
indices : planesIndices,
primitiveType : PrimitiveType.TRIANGLES,
boundingSphere : boundingSphere
},
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(Color.fromAlpha(this._color, 0.1, scratchColor))
},
id : this.id,
pickPrimitive : this
}),
appearance : new PerInstanceColorAppearance({
translucent : true,
flat : true
}),
asynchronous : false
});
}
this._outlinePrimitive.update(frameState);
this._planesPrimitive.update(frameState);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see DebugCameraPrimitive#destroy
*/
DebugCameraPrimitive.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* p = p && p.destroy();
*
* @see DebugCameraPrimitive#isDestroyed
*/
DebugCameraPrimitive.prototype.destroy = function() {
this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy();
this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy();
return destroyObject(this);
};
return DebugCameraPrimitive;
});
/*global define*/
define('Scene/DebugModelMatrixPrimitive',[
'../Core/Cartesian3',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/GeometryInstance',
'../Core/Matrix4',
'../Core/PolylineGeometry',
'./PolylineColorAppearance',
'./Primitive'
], function(
Cartesian3,
Color,
defaultValue,
defined,
destroyObject,
GeometryInstance,
Matrix4,
PolylineGeometry,
PolylineColorAppearance,
Primitive) {
'use strict';
/**
* Draws the axes of a reference frame defined by a matrix that transforms to world
* coordinates, i.e., Earth's WGS84 coordinates. The most prominent example is
* a primitives modelMatrix
.
*
* The X axis is red; Y is green; and Z is blue.
*
*
* This is for debugging only; it is not optimized for production use.
*
*
* @alias DebugModelMatrixPrimitive
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.length=10000000.0] The length of the axes in meters.
* @param {Number} [options.width=2.0] The width of the axes in pixels.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 matrix that defines the reference frame, i.e., origin plus axes, to visualize.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
* @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
*
* @example
* primitives.add(new Cesium.DebugModelMatrixPrimitive({
* modelMatrix : primitive.modelMatrix, // primitive to debug
* length : 100000.0,
* width : 10.0
* }));
*/
function DebugModelMatrixPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The length of the axes in meters.
*
* @type {Number}
* @default 10000000.0
*/
this.length = defaultValue(options.length, 10000000.0);
this._length = undefined;
/**
* The width of the axes in pixels.
*
* @type {Number}
* @default 2.0
*/
this.width = defaultValue(options.width, 2.0);
this._width = undefined;
/**
* Determines if this primitive will be shown.
*
* @type Boolean
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* The 4x4 matrix that defines the reference frame, i.e., origin plus axes, to visualize.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = new Matrix4();
/**
* User-defined object returned when the primitive is picked.
*
* @type {Object}
* @default undefined
*
* @see Scene#pick
*/
this.id = options.id;
this._id = undefined;
this._primitive = undefined;
}
/**
* @private
*/
DebugModelMatrixPrimitive.prototype.update = function(frameState) {
if (!this.show) {
return;
}
if (!defined(this._primitive) ||
(!Matrix4.equals(this._modelMatrix, this.modelMatrix)) ||
(this._length !== this.length) ||
(this._width !== this.width) ||
(this._id !== this.id)) {
this._modelMatrix = Matrix4.clone(this.modelMatrix, this._modelMatrix);
this._length = this.length;
this._width = this.width;
this._id = this.id;
if (defined(this._primitive)) {
this._primitive.destroy();
}
// Workaround projecting (0, 0, 0)
if ((this.modelMatrix[12] === 0.0 && this.modelMatrix[13] === 0.0 && this.modelMatrix[14] === 0.0)) {
this.modelMatrix[14] = 0.01;
}
var x = new GeometryInstance({
geometry : new PolylineGeometry({
positions : [
Cartesian3.ZERO,
Cartesian3.UNIT_X
],
width : this.width,
vertexFormat : PolylineColorAppearance.VERTEX_FORMAT,
colors : [
Color.RED,
Color.RED
],
followSurface: false
}),
modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
id : this.id,
pickPrimitive : this
});
var y = new GeometryInstance({
geometry : new PolylineGeometry({
positions : [
Cartesian3.ZERO,
Cartesian3.UNIT_Y
],
width : this.width,
vertexFormat : PolylineColorAppearance.VERTEX_FORMAT,
colors : [
Color.GREEN,
Color.GREEN
],
followSurface: false
}),
modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
id : this.id,
pickPrimitive : this
});
var z = new GeometryInstance({
geometry : new PolylineGeometry({
positions : [
Cartesian3.ZERO,
Cartesian3.UNIT_Z
],
width : this.width,
vertexFormat : PolylineColorAppearance.VERTEX_FORMAT,
colors : [
Color.BLUE,
Color.BLUE
],
followSurface: false
}),
modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
id : this.id,
pickPrimitive : this
});
this._primitive = new Primitive({
geometryInstances : [x, y, z],
appearance : new PolylineColorAppearance(),
asynchronous : false
});
}
this._primitive.update(frameState);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see DebugModelMatrixPrimitive#destroy
*/
DebugModelMatrixPrimitive.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* p = p && p.destroy();
*
* @see DebugModelMatrixPrimitive#isDestroyed
*/
DebugModelMatrixPrimitive.prototype.destroy = function() {
this._primitive = this._primitive && this._primitive.destroy();
return destroyObject(this);
};
return DebugModelMatrixPrimitive;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/DepthPlaneFS',[],function() {
'use strict';
return "varying vec4 positionEC;\n\
void main()\n\
{\n\
czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC();\n\
vec3 direction = normalize(positionEC.xyz);\n\
czm_ray ray = czm_ray(vec3(0.0), direction);\n\
czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid);\n\
if (!czm_isEmpty(intersection))\n\
{\n\
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n\
}\n\
else\n\
{\n\
discard;\n\
}\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/DepthPlaneVS',[],function() {
'use strict';
return "attribute vec4 position;\n\
varying vec4 positionEC;\n\
void main()\n\
{\n\
positionEC = czm_modelView * position;\n\
gl_Position = czm_projection * positionEC;\n\
}\n\
";
});
/*global define*/
define('Scene/DepthPlane',[
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/ComponentDatatype',
'../Core/defined',
'../Core/FeatureDetection',
'../Core/Geometry',
'../Core/GeometryAttribute',
'../Core/PrimitiveType',
'../Renderer/BufferUsage',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/VertexArray',
'../Shaders/DepthPlaneFS',
'../Shaders/DepthPlaneVS',
'./DepthFunction',
'./SceneMode'
], function(
BoundingSphere,
Cartesian3,
ComponentDatatype,
defined,
FeatureDetection,
Geometry,
GeometryAttribute,
PrimitiveType,
BufferUsage,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
VertexArray,
DepthPlaneFS,
DepthPlaneVS,
DepthFunction,
SceneMode) {
'use strict';
/**
* @private
*/
function DepthPlane() {
this._rs = undefined;
this._sp = undefined;
this._va = undefined;
this._command = undefined;
this._mode = undefined;
}
var depthQuadScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(12) : [];
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4 = new Cartesian3();
function computeDepthQuad(ellipsoid, frameState) {
var radii = ellipsoid.radii;
var p = frameState.camera.positionWC;
// Find the corresponding position in the scaled space of the ellipsoid.
var q = Cartesian3.multiplyComponents(ellipsoid.oneOverRadii, p, scratchCartesian1);
var qMagnitude = Cartesian3.magnitude(q);
var qUnit = Cartesian3.normalize(q, scratchCartesian2);
// Determine the east and north directions at q.
var eUnit = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, q, scratchCartesian3), scratchCartesian3);
var nUnit = Cartesian3.normalize(Cartesian3.cross(qUnit, eUnit, scratchCartesian4), scratchCartesian4);
// Determine the radius of the 'limb' of the ellipsoid.
var wMagnitude = Math.sqrt(Cartesian3.magnitudeSquared(q) - 1.0);
// Compute the center and offsets.
var center = Cartesian3.multiplyByScalar(qUnit, 1.0 / qMagnitude, scratchCartesian1);
var scalar = wMagnitude / qMagnitude;
var eastOffset = Cartesian3.multiplyByScalar(eUnit, scalar, scratchCartesian2);
var northOffset = Cartesian3.multiplyByScalar(nUnit, scalar, scratchCartesian3);
// A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum.
var upperLeft = Cartesian3.add(center, northOffset, scratchCartesian4);
Cartesian3.subtract(upperLeft, eastOffset, upperLeft);
Cartesian3.multiplyComponents(radii, upperLeft, upperLeft);
Cartesian3.pack(upperLeft, depthQuadScratch, 0);
var lowerLeft = Cartesian3.subtract(center, northOffset, scratchCartesian4);
Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft);
Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft);
Cartesian3.pack(lowerLeft, depthQuadScratch, 3);
var upperRight = Cartesian3.add(center, northOffset, scratchCartesian4);
Cartesian3.add(upperRight, eastOffset, upperRight);
Cartesian3.multiplyComponents(radii, upperRight, upperRight);
Cartesian3.pack(upperRight, depthQuadScratch, 6);
var lowerRight = Cartesian3.subtract(center, northOffset, scratchCartesian4);
Cartesian3.add(lowerRight, eastOffset, lowerRight);
Cartesian3.multiplyComponents(radii, lowerRight, lowerRight);
Cartesian3.pack(lowerRight, depthQuadScratch, 9);
return depthQuadScratch;
}
DepthPlane.prototype.update = function(frameState) {
this._mode = frameState.mode;
if (frameState.mode !== SceneMode.SCENE3D) {
return;
}
var context = frameState.context;
var ellipsoid = frameState.mapProjection.ellipsoid;
if (!defined(this._command)) {
this._rs = RenderState.fromCache({ // Write depth, not color
cull : {
enabled : true
},
depthTest : {
enabled : true,
func : DepthFunction.ALWAYS
},
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
}
});
this._sp = ShaderProgram.fromCache({
context : context,
vertexShaderSource : DepthPlaneVS,
fragmentShaderSource : DepthPlaneFS,
attributeLocations : {
position : 0
}
});
this._command = new DrawCommand({
renderState : this._rs,
shaderProgram : this._sp,
boundingVolume : new BoundingSphere(Cartesian3.ZERO, ellipsoid.maximumRadius),
pass : Pass.OPAQUE,
owner : this
});
}
// update depth plane
var depthQuad = computeDepthQuad(ellipsoid, frameState);
// depth plane
if (!defined(this._va)) {
var geometry = new Geometry({
attributes : {
position : new GeometryAttribute({
componentDatatype : ComponentDatatype.FLOAT,
componentsPerAttribute : 3,
values : depthQuad
})
},
indices : [0, 1, 2, 2, 1, 3],
primitiveType : PrimitiveType.TRIANGLES
});
this._va = VertexArray.fromGeometry({
context : context,
geometry : geometry,
attributeLocations : {
position : 0
},
bufferUsage : BufferUsage.DYNAMIC_DRAW
});
this._command.vertexArray = this._va;
} else {
this._va.getAttribute(0).vertexBuffer.copyFromArrayView(depthQuad);
}
};
DepthPlane.prototype.execute = function(context, passState) {
if (this._mode === SceneMode.SCENE3D) {
this._command.execute(context, passState);
}
};
DepthPlane.prototype.isDestroyed = function() {
return false;
};
DepthPlane.prototype.destroy = function() {
this._sp = this._sp && this._sp.destroy();
this._va = this._va && this._va.destroy();
};
return DepthPlane;
});
/*global define*/
define('Scene/DeviceOrientationCameraController',[
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Math',
'../Core/Matrix3',
'../Core/Quaternion'
], function(
defined,
destroyObject,
DeveloperError,
CesiumMath,
Matrix3,
Quaternion) {
'use strict';
/**
* @private
*/
function DeviceOrientationCameraController(scene) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
this._scene = scene;
this._lastAlpha = undefined;
this._lastBeta = undefined;
this._lastGamma = undefined;
this._alpha = undefined;
this._beta = undefined;
this._gamma = undefined;
var that = this;
function callback(e) {
var alpha = e.alpha;
if (!defined(alpha)) {
that._alpha = undefined;
that._beta = undefined;
that._gamma = undefined;
return;
}
that._alpha = CesiumMath.toRadians(alpha);
that._beta = CesiumMath.toRadians(e.beta);
that._gamma = CesiumMath.toRadians(e.gamma);
}
window.addEventListener('deviceorientation', callback, false);
this._removeListener = function() {
window.removeEventListener('deviceorientation', callback, false);
};
}
var scratchQuaternion1 = new Quaternion();
var scratchQuaternion2 = new Quaternion();
var scratchMatrix3 = new Matrix3();
function rotate(camera, alpha, beta, gamma) {
var direction = camera.direction;
var right = camera.right;
var up = camera.up;
var bQuat = Quaternion.fromAxisAngle(direction, beta, scratchQuaternion2);
var gQuat = Quaternion.fromAxisAngle(right, gamma, scratchQuaternion1);
var rotQuat = Quaternion.multiply(gQuat, bQuat, gQuat);
var aQuat = Quaternion.fromAxisAngle(up, alpha, scratchQuaternion2);
Quaternion.multiply(aQuat, rotQuat, rotQuat);
var matrix = Matrix3.fromQuaternion(rotQuat, scratchMatrix3);
Matrix3.multiplyByVector(matrix, right, right);
Matrix3.multiplyByVector(matrix, up, up);
Matrix3.multiplyByVector(matrix, direction, direction);
}
DeviceOrientationCameraController.prototype.update = function() {
if (!defined(this._alpha)) {
return;
}
if (!defined(this._lastAlpha)) {
this._lastAlpha = this._alpha;
this._lastBeta = this._beta;
this._lastGamma = this._gamma;
}
var a = this._lastAlpha - this._alpha;
var b = this._lastBeta - this._beta;
var g = this._lastGamma - this._gamma;
rotate(this._scene.camera, -a, b, g);
this._lastAlpha = this._alpha;
this._lastBeta = this._beta;
this._lastGamma = this._gamma;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*/
DeviceOrientationCameraController.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the resources held by this object. Destroying an object allows for deterministic
* release of resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
DeviceOrientationCameraController.prototype.destroy = function() {
this._removeListener();
return destroyObject(this);
};
return DeviceOrientationCameraController;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/EllipsoidFS',[],function() {
'use strict';
return "#ifdef WRITE_DEPTH\n\
#ifdef GL_EXT_frag_depth\n\
#extension GL_EXT_frag_depth : enable\n\
#endif\n\
#endif\n\
uniform vec3 u_radii;\n\
uniform vec3 u_oneOverEllipsoidRadiiSquared;\n\
varying vec3 v_positionEC;\n\
vec4 computeEllipsoidColor(czm_ray ray, float intersection, float side)\n\
{\n\
vec3 positionEC = czm_pointAlongRay(ray, intersection);\n\
vec3 positionMC = (czm_inverseModelView * vec4(positionEC, 1.0)).xyz;\n\
vec3 geodeticNormal = normalize(czm_geodeticSurfaceNormal(positionMC, vec3(0.0), u_oneOverEllipsoidRadiiSquared));\n\
vec3 sphericalNormal = normalize(positionMC / u_radii);\n\
vec3 normalMC = geodeticNormal * side;\n\
vec3 normalEC = normalize(czm_normal * normalMC);\n\
vec2 st = czm_ellipsoidWgs84TextureCoordinates(sphericalNormal);\n\
vec3 positionToEyeEC = -positionEC;\n\
czm_materialInput materialInput;\n\
materialInput.s = st.s;\n\
materialInput.st = st;\n\
materialInput.str = (positionMC + u_radii) / u_radii;\n\
materialInput.normalEC = normalEC;\n\
materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(positionMC, normalEC);\n\
materialInput.positionToEyeEC = positionToEyeEC;\n\
czm_material material = czm_getMaterial(materialInput);\n\
#ifdef ONLY_SUN_LIGHTING\n\
return czm_private_phong(normalize(positionToEyeEC), material);\n\
#else\n\
return czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
void main()\n\
{\n\
float maxRadius = max(u_radii.x, max(u_radii.y, u_radii.z)) * 1.5;\n\
vec3 direction = normalize(v_positionEC);\n\
vec3 ellipsoidCenter = czm_modelView[3].xyz;\n\
float t1 = -1.0;\n\
float t2 = -1.0;\n\
float b = -2.0 * dot(direction, ellipsoidCenter);\n\
float c = dot(ellipsoidCenter, ellipsoidCenter) - maxRadius * maxRadius;\n\
float discriminant = b * b - 4.0 * c;\n\
if (discriminant >= 0.0) {\n\
t1 = (-b - sqrt(discriminant)) * 0.5;\n\
t2 = (-b + sqrt(discriminant)) * 0.5;\n\
}\n\
if (t1 < 0.0 && t2 < 0.0) {\n\
discard;\n\
}\n\
float t = min(t1, t2);\n\
if (t < 0.0) {\n\
t = 0.0;\n\
}\n\
czm_ellipsoid ellipsoid = czm_ellipsoidNew(ellipsoidCenter, u_radii);\n\
czm_ray ray = czm_ray(t * direction, direction);\n\
czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid);\n\
if (czm_isEmpty(intersection))\n\
{\n\
discard;\n\
}\n\
vec4 outsideFaceColor = (intersection.start != 0.0) ? computeEllipsoidColor(ray, intersection.start, 1.0) : vec4(0.0);\n\
vec4 insideFaceColor = (outsideFaceColor.a < 1.0) ? computeEllipsoidColor(ray, intersection.stop, -1.0) : vec4(0.0);\n\
gl_FragColor = mix(insideFaceColor, outsideFaceColor, outsideFaceColor.a);\n\
gl_FragColor.a = 1.0 - (1.0 - insideFaceColor.a) * (1.0 - outsideFaceColor.a);\n\
#ifdef WRITE_DEPTH\n\
#ifdef GL_EXT_frag_depth\n\
t = (intersection.start != 0.0) ? intersection.start : intersection.stop;\n\
vec3 positionEC = czm_pointAlongRay(ray, t);\n\
vec4 positionCC = czm_projection * vec4(positionEC, 1.0);\n\
float z = positionCC.z / positionCC.w;\n\
float n = czm_depthRange.near;\n\
float f = czm_depthRange.far;\n\
gl_FragDepthEXT = (z * (f - n) + f + n) * 0.5;\n\
#endif\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/EllipsoidVS',[],function() {
'use strict';
return "attribute vec3 position;\n\
uniform vec3 u_radii;\n\
varying vec3 v_positionEC;\n\
void main()\n\
{\n\
vec4 p = vec4(u_radii * position, 1.0);\n\
v_positionEC = (czm_modelView * p).xyz;\n\
gl_Position = czm_modelViewProjection * p;\n\
gl_Position.z = clamp(gl_Position.z, czm_depthRange.near, czm_depthRange.far);\n\
}\n\
";
});
/*global define*/
define('Scene/EllipsoidPrimitive',[
'../Core/BoundingSphere',
'../Core/BoxGeometry',
'../Core/Cartesian3',
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Matrix4',
'../Core/VertexFormat',
'../Renderer/BufferUsage',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/VertexArray',
'../Shaders/EllipsoidFS',
'../Shaders/EllipsoidVS',
'./BlendingState',
'./CullFace',
'./Material',
'./SceneMode'
], function(
BoundingSphere,
BoxGeometry,
Cartesian3,
combine,
defaultValue,
defined,
destroyObject,
DeveloperError,
Matrix4,
VertexFormat,
BufferUsage,
DrawCommand,
Pass,
RenderState,
ShaderProgram,
ShaderSource,
VertexArray,
EllipsoidFS,
EllipsoidVS,
BlendingState,
CullFace,
Material,
SceneMode) {
'use strict';
var attributeLocations = {
position : 0
};
/**
* A renderable ellipsoid. It can also draw spheres when the three {@link EllipsoidPrimitive#radii} components are equal.
*
* This is only supported in 3D. The ellipsoid is not shown in 2D or Columbus view.
*
*
* @alias EllipsoidPrimitive
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Cartesian3} [options.center=Cartesian3.ZERO] The center of the ellipsoid in the ellipsoid's model coordinates.
* @param {Cartesian3} [options.radii] The radius of the ellipsoid along the x
, y
, and z
axes in the ellipsoid's model coordinates.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
* @param {Material} [options.material=Material.ColorType] The surface appearance of the primitive.
* @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
*
* @private
*/
function EllipsoidPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
/**
* The center of the ellipsoid in the ellipsoid's model coordinates.
*
* The default is {@link Cartesian3.ZERO}.
*
*
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*
* @see EllipsoidPrimitive#modelMatrix
*/
this.center = Cartesian3.clone(defaultValue(options.center, Cartesian3.ZERO));
this._center = new Cartesian3();
/**
* The radius of the ellipsoid along the x
, y
, and z
axes in the ellipsoid's model coordinates.
* When these are the same, the ellipsoid is a sphere.
*
* The default is undefined
. The ellipsoid is not drawn until a radii is provided.
*
*
* @type {Cartesian3}
* @default undefined
*
*
* @example
* // A sphere with a radius of 2.0
* e.radii = new Cesium.Cartesian3(2.0, 2.0, 2.0);
*
* @see EllipsoidPrimitive#modelMatrix
*/
this.radii = Cartesian3.clone(options.radii);
this._radii = new Cartesian3();
this._oneOverEllipsoidRadiiSquared = new Cartesian3();
this._boundingSphere = new BoundingSphere();
/**
* The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
* When this is the identity matrix, the ellipsoid is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
* @default {@link Matrix4.IDENTITY}
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* e.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = new Matrix4();
this._computedModelMatrix = new Matrix4();
/**
* Determines if the ellipsoid primitive will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* The surface appearance of the ellipsoid. This can be one of several built-in {@link Material} objects or a custom material, scripted with
* {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
*
* The default material is Material.ColorType
.
*
*
* @type {Material}
* @default Material.fromType(Material.ColorType)
*
*
* @example
* // 1. Change the color of the default material to yellow
* e.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
*
* // 2. Change material to horizontal stripes
* e.material = Cesium.Material.fromType(Cesium.Material.StripeType);
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = defaultValue(options.material, Material.fromType(Material.ColorType));
this._material = undefined;
this._translucent = undefined;
/**
* User-defined object returned when the ellipsoid is picked.
*
* @type Object
*
* @default undefined
*
* @see Scene#pick
*/
this.id = options.id;
this._id = undefined;
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the primitive.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
/**
* @private
*/
this.onlySunLighting = defaultValue(options.onlySunLighting, false);
this._onlySunLighting = false;
/**
* @private
*/
this._depthTestEnabled = defaultValue(options.depthTestEnabled, true);
this._sp = undefined;
this._rs = undefined;
this._va = undefined;
this._pickSP = undefined;
this._pickId = undefined;
this._colorCommand = new DrawCommand({
owner : defaultValue(options._owner, this)
});
this._pickCommand = new DrawCommand({
owner : defaultValue(options._owner, this)
});
var that = this;
this._uniforms = {
u_radii : function() {
return that.radii;
},
u_oneOverEllipsoidRadiiSquared : function() {
return that._oneOverEllipsoidRadiiSquared;
}
};
this._pickUniforms = {
czm_pickColor : function() {
return that._pickId.color;
}
};
}
function getVertexArray(context) {
var vertexArray = context.cache.ellipsoidPrimitive_vertexArray;
if (defined(vertexArray)) {
return vertexArray;
}
var geometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
dimensions : new Cartesian3(2.0, 2.0, 2.0),
vertexFormat : VertexFormat.POSITION_ONLY
}));
vertexArray = VertexArray.fromGeometry({
context : context,
geometry : geometry,
attributeLocations : attributeLocations,
bufferUsage : BufferUsage.STATIC_DRAW,
interleave : true
});
context.cache.ellipsoidPrimitive_vertexArray = vertexArray;
return vertexArray;
}
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {DeveloperError} this.material must be defined.
*/
EllipsoidPrimitive.prototype.update = function(frameState) {
if (!this.show ||
(frameState.mode !== SceneMode.SCENE3D) ||
(!defined(this.center)) ||
(!defined(this.radii))) {
return;
}
if (!defined(this.material)) {
throw new DeveloperError('this.material must be defined.');
}
var context = frameState.context;
var translucent = this.material.isTranslucent();
var translucencyChanged = this._translucent !== translucent;
if (!defined(this._rs) || translucencyChanged) {
this._translucent = translucent;
// If this render state is ever updated to use a non-default
// depth range, the hard-coded values in EllipsoidVS.glsl need
// to be updated as well.
this._rs = RenderState.fromCache({
// Cull front faces - not back faces - so the ellipsoid doesn't
// disappear if the viewer enters the bounding box.
cull : {
enabled : true,
face : CullFace.FRONT
},
depthTest : {
enabled : this._depthTestEnabled
},
// Only write depth when EXT_frag_depth is supported since the depth for
// the bounding box is wrong; it is not the true depth of the ray casted ellipsoid.
depthMask : !translucent && context.fragmentDepth,
blending : translucent ? BlendingState.ALPHA_BLEND : undefined
});
}
if (!defined(this._va)) {
this._va = getVertexArray(context);
}
var boundingSphereDirty = false;
var radii = this.radii;
if (!Cartesian3.equals(this._radii, radii)) {
Cartesian3.clone(radii, this._radii);
var r = this._oneOverEllipsoidRadiiSquared;
r.x = 1.0 / (radii.x * radii.x);
r.y = 1.0 / (radii.y * radii.y);
r.z = 1.0 / (radii.z * radii.z);
boundingSphereDirty = true;
}
if (!Matrix4.equals(this.modelMatrix, this._modelMatrix) || !Cartesian3.equals(this.center, this._center)) {
Matrix4.clone(this.modelMatrix, this._modelMatrix);
Cartesian3.clone(this.center, this._center);
// Translate model coordinates used for rendering such that the origin is the center of the ellipsoid.
Matrix4.multiplyByTranslation(this.modelMatrix, this.center, this._computedModelMatrix);
boundingSphereDirty = true;
}
if (boundingSphereDirty) {
Cartesian3.clone(Cartesian3.ZERO, this._boundingSphere.center);
this._boundingSphere.radius = Cartesian3.maximumComponent(radii);
BoundingSphere.transform(this._boundingSphere, this._computedModelMatrix, this._boundingSphere);
}
var materialChanged = this._material !== this.material;
this._material = this.material;
this._material.update(context);
var lightingChanged = this.onlySunLighting !== this._onlySunLighting;
this._onlySunLighting = this.onlySunLighting;
var colorCommand = this._colorCommand;
var fs;
// Recompile shader when material, lighting, or translucency changes
if (materialChanged || lightingChanged || translucencyChanged) {
fs = new ShaderSource({
sources : [this.material.shaderSource, EllipsoidFS]
});
if (this.onlySunLighting) {
fs.defines.push('ONLY_SUN_LIGHTING');
}
if (!translucent && context.fragmentDepth) {
fs.defines.push('WRITE_DEPTH');
}
this._sp = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._sp,
vertexShaderSource : EllipsoidVS,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
colorCommand.vertexArray = this._va;
colorCommand.renderState = this._rs;
colorCommand.shaderProgram = this._sp;
colorCommand.uniformMap = combine(this._uniforms, this.material._uniforms);
colorCommand.executeInClosestFrustum = translucent;
}
var commandList = frameState.commandList;
var passes = frameState.passes;
if (passes.render) {
colorCommand.boundingVolume = this._boundingSphere;
colorCommand.debugShowBoundingVolume = this.debugShowBoundingVolume;
colorCommand.modelMatrix = this._computedModelMatrix;
colorCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
commandList.push(colorCommand);
}
if (passes.pick) {
var pickCommand = this._pickCommand;
if (!defined(this._pickId) || (this._id !== this.id)) {
this._id = this.id;
this._pickId = this._pickId && this._pickId.destroy();
this._pickId = context.createPickId({
primitive : this,
id : this.id
});
}
// Recompile shader when material changes
if (materialChanged || lightingChanged || !defined(this._pickSP)) {
fs = new ShaderSource({
sources : [this.material.shaderSource, EllipsoidFS],
pickColorQualifier : 'uniform'
});
if (this.onlySunLighting) {
fs.defines.push('ONLY_SUN_LIGHTING');
}
if (!translucent && context.fragmentDepth) {
fs.defines.push('WRITE_DEPTH');
}
this._pickSP = ShaderProgram.replaceCache({
context : context,
shaderProgram : this._pickSP,
vertexShaderSource : EllipsoidVS,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
pickCommand.vertexArray = this._va;
pickCommand.renderState = this._rs;
pickCommand.shaderProgram = this._pickSP;
pickCommand.uniformMap = combine(combine(this._uniforms, this._pickUniforms), this.material._uniforms);
pickCommand.executeInClosestFrustum = translucent;
}
pickCommand.boundingVolume = this._boundingSphere;
pickCommand.modelMatrix = this._computedModelMatrix;
pickCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
commandList.push(pickCommand);
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see EllipsoidPrimitive#destroy
*/
EllipsoidPrimitive.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* e = e && e.destroy();
*
* @see EllipsoidPrimitive#isDestroyed
*/
EllipsoidPrimitive.prototype.destroy = function() {
this._sp = this._sp && this._sp.destroy();
this._pickSP = this._pickSP && this._pickSP.destroy();
this._pickId = this._pickId && this._pickId.destroy();
return destroyObject(this);
};
return EllipsoidPrimitive;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/EllipsoidSurfaceAppearanceFS',[],function() {
'use strict';
return "varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
czm_materialInput materialInput;\n\
vec3 normalEC = normalize(czm_normal3D * czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0)));\n\
#ifdef FACE_FORWARD\n\
normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
materialInput.s = v_st.s;\n\
materialInput.st = v_st;\n\
materialInput.str = vec3(v_st, 0.0);\n\
materialInput.normalEC = normalEC;\n\
materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, materialInput.normalEC);\n\
vec3 positionToEyeEC = -v_positionEC;\n\
materialInput.positionToEyeEC = positionToEyeEC;\n\
czm_material material = czm_getMaterial(materialInput);\n\
#ifdef FLAT\n\
gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/EllipsoidSurfaceAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec2 v_st;\n\
void main()\n\
{\n\
vec4 p = czm_computePosition();\n\
v_positionMC = position3DHigh + position3DLow;\n\
v_positionEC = (czm_modelViewRelativeToEye * p).xyz;\n\
v_st = st;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/EllipsoidSurfaceAppearance',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/VertexFormat',
'../Shaders/Appearances/EllipsoidSurfaceAppearanceFS',
'../Shaders/Appearances/EllipsoidSurfaceAppearanceVS',
'./Appearance',
'./Material'
], function(
defaultValue,
defined,
defineProperties,
VertexFormat,
EllipsoidSurfaceAppearanceFS,
EllipsoidSurfaceAppearanceVS,
Appearance,
Material) {
'use strict';
/**
* An appearance for geometry on the surface of the ellipsoid like {@link PolygonGeometry}
* and {@link RectangleGeometry}, which supports all materials like {@link MaterialAppearance}
* with {@link MaterialAppearance.MaterialSupport.ALL}. However, this appearance requires
* fewer vertex attributes since the fragment shader can procedurally compute normal
,
* binormal
, and tangent
.
*
* @alias EllipsoidSurfaceAppearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.flat=false] When true
, flat shading is used in the fragment shader, which means lighting is not taking into account.
* @param {Boolean} [options.faceForward=options.aboveGround] When true
, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots. This is useful when both sides of a geometry should be shaded like {@link WallGeometry}.
* @param {Boolean} [options.translucent=true] When true
, the geometry is expected to appear translucent so {@link EllipsoidSurfaceAppearance#renderState} has alpha blending enabled.
* @param {Boolean} [options.aboveGround=false] When true
, the geometry is expected to be on the ellipsoid's surface - not at a constant height above it - so {@link EllipsoidSurfaceAppearance#renderState} has backface culling enabled.
* @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*
* @example
* var primitive = new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : new Cesium.PolygonGeometry({
* vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
* // ...
* })
* }),
* appearance : new Cesium.EllipsoidSurfaceAppearance({
* material : Cesium.Material.fromType('Stripe')
* })
* });
*/
function EllipsoidSurfaceAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var translucent = defaultValue(options.translucent, true);
var aboveGround = defaultValue(options.aboveGround, false);
/**
* The material used to determine the fragment color. Unlike other {@link EllipsoidSurfaceAppearance}
* properties, this is not read-only, so an appearance's material can change on the fly.
*
* @type Material
*
* @default {@link Material.ColorType}
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = (defined(options.material)) ? options.material : Material.fromType(Material.ColorType);
/**
* When true
, the geometry is expected to appear translucent.
*
* @type {Boolean}
*
* @default true
*/
this.translucent = defaultValue(options.translucent, true);
this._vertexShaderSource = defaultValue(options.vertexShaderSource, EllipsoidSurfaceAppearanceVS);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, EllipsoidSurfaceAppearanceFS);
this._renderState = Appearance.getDefaultRenderState(translucent, !aboveGround, options.renderState);
this._closed = false;
// Non-derived members
this._flat = defaultValue(options.flat, false);
this._faceForward = defaultValue(options.faceForward, aboveGround);
this._aboveGround = aboveGround;
}
defineProperties(EllipsoidSurfaceAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader. The full fragment shader
* source is built procedurally taking into account {@link EllipsoidSurfaceAppearance#material},
* {@link EllipsoidSurfaceAppearance#flat}, and {@link EllipsoidSurfaceAppearance#faceForward}.
* Use {@link EllipsoidSurfaceAppearance#getFragmentShaderSource} to get the full source.
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* The render state can be explicitly defined when constructing a {@link EllipsoidSurfaceAppearance}
* instance, or it is set implicitly via {@link EllipsoidSurfaceAppearance#translucent}
* and {@link EllipsoidSurfaceAppearance#aboveGround}.
*
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed so
* {@link EllipsoidSurfaceAppearance#renderState} has backface culling enabled.
* If the viewer enters the geometry, it will not be visible.
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return this._closed;
}
},
/**
* The {@link VertexFormat} that this appearance instance is compatible with.
* A geometry can have more vertex attributes and still be compatible - at a
* potential performance cost - but it can't have less.
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type VertexFormat
* @readonly
*
* @default {@link EllipsoidSurfaceAppearance.VERTEX_FORMAT}
*/
vertexFormat : {
get : function() {
return EllipsoidSurfaceAppearance.VERTEX_FORMAT;
}
},
/**
* When true
, flat shading is used in the fragment shader,
* which means lighting is not taking into account.
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
flat : {
get : function() {
return this._flat;
}
},
/**
* When true
, the fragment shader flips the surface normal
* as needed to ensure that the normal faces the viewer to avoid
* dark spots. This is useful when both sides of a geometry should be
* shaded like {@link WallGeometry}.
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default true
*/
faceForward : {
get : function() {
return this._faceForward;
}
},
/**
* When true
, the geometry is expected to be on the ellipsoid's
* surface - not at a constant height above it - so {@link EllipsoidSurfaceAppearance#renderState}
* has backface culling enabled.
*
*
* @memberof EllipsoidSurfaceAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
aboveGround : {
get : function() {
return this._aboveGround;
}
}
});
/**
* The {@link VertexFormat} that all {@link EllipsoidSurfaceAppearance} instances
* are compatible with, which requires only position
and st
* attributes. Other attributes are procedurally computed in the fragment shader.
*
* @type VertexFormat
*
* @constant
*/
EllipsoidSurfaceAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_ST;
/**
* Procedurally creates the full GLSL fragment shader source. For {@link EllipsoidSurfaceAppearance},
* this is derived from {@link EllipsoidSurfaceAppearance#fragmentShaderSource}, {@link EllipsoidSurfaceAppearance#flat},
* and {@link EllipsoidSurfaceAppearance#faceForward}.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
EllipsoidSurfaceAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link EllipsoidSurfaceAppearance#translucent} and {@link Material#isTranslucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
EllipsoidSurfaceAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
EllipsoidSurfaceAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
return EllipsoidSurfaceAppearance;
});
/*global define*/
define('Scene/Fog',[
'../Core/Cartesian3',
'../Core/defined',
'../Core/Math',
'./SceneMode'
], function(
Cartesian3,
defined,
CesiumMath,
SceneMode) {
'use strict';
/**
* Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional
* performance improvements by rendering less geometry and dispatching less terrain requests.
*
* @alias Fog
* @constructor
*/
function Fog() {
/**
* true
if fog is enabled, false
otherwise.
* @type {Boolean}
* @default true
*/
this.enabled = true;
/**
* A scalar that determines the density of the fog. Terrain that is in full fog are culled.
* The density of the fog increases as this number approaches 1.0 and becomes less dense as it approaches zero.
* The more dense the fog is, the more aggressively the terrain is culled. For example, if the camera is a height of
* 1000.0m above the ellipsoid, increasing the value to 3.0e-3 will cause many tiles close to the viewer be culled.
* Decreasing the value will push the fog further from the viewer, but decrease performance as more of the terrain is rendered.
* @type {Number}
* @default 2.0e-4
*/
this.density = 2.0e-4;
/**
* A factor used to increase the screen space error of terrain tiles when they are partially in fog. The effect is to reduce
* the number of terrain tiles requested for rendering. If set to zero, the feature will be disabled. If the value is increased
* for mountainous regions, less tiles will need to be requested, but the terrain meshes near the horizon may be a noticeably
* lower resolution. If the value is increased in a relatively flat area, there will be little noticeable change on the horizon.
* @type {Number}
* @default 2.0
*/
this.screenSpaceErrorFactor = 2.0;
}
// These values were found by sampling the density at certain views and finding at what point culled tiles impacted the view at the horizon.
var heightsTable = [359.393, 800.749, 1275.6501, 2151.1192, 3141.7763, 4777.5198, 6281.2493, 12364.307, 15900.765, 49889.0549, 78026.8259, 99260.7344, 120036.3873, 151011.0158, 156091.1953, 203849.3112, 274866.9803, 319916.3149, 493552.0528, 628733.5874];
var densityTable = [2.0e-5, 2.0e-4, 1.0e-4, 7.0e-5, 5.0e-5, 4.0e-5, 3.0e-5, 1.9e-5, 1.0e-5, 8.5e-6, 6.2e-6, 5.8e-6, 5.3e-6, 5.2e-6, 5.1e-6, 4.2e-6, 4.0e-6, 3.4e-6, 2.6e-6, 2.2e-6];
// Scale densities by 1e6 to bring lowest value to ~1. Prevents divide by zero.
for (var i = 0; i < densityTable.length; ++i) {
densityTable[i] *= 1.0e6;
}
// Change range to [0, 1].
var tableStartDensity = densityTable[1];
var tableEndDensity = densityTable[densityTable.length - 1];
for (var j = 0; j < densityTable.length; ++j) {
densityTable[j] = (densityTable[j] - tableEndDensity) / (tableStartDensity - tableEndDensity);
}
var tableLastIndex = 0;
function findInterval(height) {
var heights = heightsTable;
var length = heights.length;
if (height < heights[0]) {
tableLastIndex = 0;
return tableLastIndex;
} else if (height > heights[length - 1]) {
tableLastIndex = length - 2;
return tableLastIndex;
}
// Take advantage of temporal coherence by checking current, next and previous intervals
// for containment of time.
if (height >= heights[tableLastIndex]) {
if (tableLastIndex + 1 < length && height < heights[tableLastIndex + 1]) {
return tableLastIndex;
} else if (tableLastIndex + 2 < length && height < heights[tableLastIndex + 2]) {
++tableLastIndex;
return tableLastIndex;
}
} else if (tableLastIndex - 1 >= 0 && height >= heights[tableLastIndex - 1]) {
--tableLastIndex;
return tableLastIndex;
}
// The above failed so do a linear search.
var i;
for (i = 0; i < length - 2; ++i) {
if (height >= heights[i] && height < heights[i + 1]) {
break;
}
}
tableLastIndex = i;
return tableLastIndex;
}
var scratchPositionNormal = new Cartesian3();
Fog.prototype.update = function(frameState) {
var enabled = frameState.fog.enabled = this.enabled;
if (!enabled) {
return;
}
var camera = frameState.camera;
var positionCartographic = camera.positionCartographic;
// Turn off fog in space.
if (!defined(positionCartographic) || positionCartographic.height > 800000.0 || frameState.mode !== SceneMode.SCENE3D) {
frameState.fog.enabled = false;
return;
}
var height = positionCartographic.height;
var i = findInterval(height);
var t = CesiumMath.clamp((height - heightsTable[i]) / (heightsTable[i + 1] - heightsTable[i]), 0.0, 1.0);
var density = CesiumMath.lerp(densityTable[i], densityTable[i + 1], t);
// Again, scale value to be in the range of densityTable (prevents divide by zero) and change to new range.
var startDensity = this.density * 1.0e6;
var endDensity = (startDensity / tableStartDensity) * tableEndDensity;
density = (density * (startDensity - endDensity)) * 1.0e-6;
// Fade fog in as the camera tilts toward the horizon.
var positionNormal = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
var dot = CesiumMath.clamp(Cartesian3.dot(camera.directionWC, positionNormal), 0.0, 1.0);
density *= 1.0 - dot;
frameState.fog.density = density;
frameState.fog.sse = this.screenSpaceErrorFactor;
};
return Fog;
});
/*global define*/
define('Scene/FrameRateMonitor',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Event',
'../Core/getTimestamp',
'../Core/TimeConstants'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Event,
getTimestamp,
TimeConstants) {
'use strict';
/**
* Monitors the frame rate (frames per second) in a {@link Scene} and raises an event if the frame rate is
* lower than a threshold. Later, if the frame rate returns to the required level, a separate event is raised.
* To avoid creating multiple FrameRateMonitors for a single {@link Scene}, use {@link FrameRateMonitor.fromScene}
* instead of constructing an instance explicitly.
*
* @alias FrameRateMonitor
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Scene} options.scene The Scene instance for which to monitor performance.
* @param {Number} [options.samplingWindow=5.0] The length of the sliding window over which to compute the average frame rate, in seconds.
* @param {Number} [options.quietPeriod=2.0] The length of time to wait at startup and each time the page becomes visible (i.e. when the user
* switches back to the tab) before starting to measure performance, in seconds.
* @param {Number} [options.warmupPeriod=5.0] The length of the warmup period, in seconds. During the warmup period, a separate
* (usually lower) frame rate is required.
* @param {Number} [options.minimumFrameRateDuringWarmup=4] The minimum frames-per-second that are required for acceptable performance during
* the warmup period. If the frame rate averages less than this during any samplingWindow during the warmupPeriod, the
* lowFrameRate event will be raised and the page will redirect to the redirectOnLowFrameRateUrl, if any.
* @param {Number} [options.minimumFrameRateAfterWarmup=8] The minimum frames-per-second that are required for acceptable performance after
* the end of the warmup period. If the frame rate averages less than this during any samplingWindow after the warmupPeriod, the
* lowFrameRate event will be raised and the page will redirect to the redirectOnLowFrameRateUrl, if any.
*/
function FrameRateMonitor(options) {
if (!defined(options) || !defined(options.scene)) {
throw new DeveloperError('options.scene is required.');
}
this._scene = options.scene;
/**
* Gets or sets the length of the sliding window over which to compute the average frame rate, in seconds.
* @type {Number}
*/
this.samplingWindow = defaultValue(options.samplingWindow, FrameRateMonitor.defaultSettings.samplingWindow);
/**
* Gets or sets the length of time to wait at startup and each time the page becomes visible (i.e. when the user
* switches back to the tab) before starting to measure performance, in seconds.
* @type {Number}
*/
this.quietPeriod = defaultValue(options.quietPeriod, FrameRateMonitor.defaultSettings.quietPeriod);
/**
* Gets or sets the length of the warmup period, in seconds. During the warmup period, a separate
* (usually lower) frame rate is required.
* @type {Number}
*/
this.warmupPeriod = defaultValue(options.warmupPeriod, FrameRateMonitor.defaultSettings.warmupPeriod);
/**
* Gets or sets the minimum frames-per-second that are required for acceptable performance during
* the warmup period. If the frame rate averages less than this during any samplingWindow
during the warmupPeriod
, the
* lowFrameRate
event will be raised and the page will redirect to the redirectOnLowFrameRateUrl
, if any.
* @type {Number}
*/
this.minimumFrameRateDuringWarmup = defaultValue(options.minimumFrameRateDuringWarmup, FrameRateMonitor.defaultSettings.minimumFrameRateDuringWarmup);
/**
* Gets or sets the minimum frames-per-second that are required for acceptable performance after
* the end of the warmup period. If the frame rate averages less than this during any samplingWindow
after the warmupPeriod
, the
* lowFrameRate
event will be raised and the page will redirect to the redirectOnLowFrameRateUrl
, if any.
* @type {Number}
*/
this.minimumFrameRateAfterWarmup = defaultValue(options.minimumFrameRateAfterWarmup, FrameRateMonitor.defaultSettings.minimumFrameRateAfterWarmup);
this._lowFrameRate = new Event();
this._nominalFrameRate = new Event();
this._frameTimes = [];
this._needsQuietPeriod = true;
this._quietPeriodEndTime = 0.0;
this._warmupPeriodEndTime = 0.0;
this._frameRateIsLow = false;
this._lastFramesPerSecond = undefined;
this._pauseCount = 0;
var that = this;
this._preRenderRemoveListener = this._scene.preRender.addEventListener(function(scene, time) {
update(that, time);
});
this._hiddenPropertyName = (document.hidden !== undefined) ? 'hidden' :
(document.mozHidden !== undefined) ? 'mozHidden' :
(document.msHidden !== undefined) ? 'msHidden' :
(document.webkitHidden !== undefined) ? 'webkitHidden' : undefined;
var visibilityChangeEventName = (document.hidden !== undefined) ? 'visibilitychange' :
(document.mozHidden !== undefined) ? 'mozvisibilitychange' :
(document.msHidden !== undefined) ? 'msvisibilitychange' :
(document.webkitHidden !== undefined) ? 'webkitvisibilitychange' : undefined;
function visibilityChangeListener() {
visibilityChanged(that);
}
this._visibilityChangeRemoveListener = undefined;
if (defined(visibilityChangeEventName)) {
document.addEventListener(visibilityChangeEventName, visibilityChangeListener, false);
this._visibilityChangeRemoveListener = function() {
document.removeEventListener(visibilityChangeEventName, visibilityChangeListener, false);
};
}
}
/**
* The default frame rate monitoring settings. These settings are used when {@link FrameRateMonitor.fromScene}
* needs to create a new frame rate monitor, and for any settings that are not passed to the
* {@link FrameRateMonitor} constructor.
*
* @memberof FrameRateMonitor
* @type {Object}
*/
FrameRateMonitor.defaultSettings = {
samplingWindow : 5.0,
quietPeriod : 2.0,
warmupPeriod : 5.0,
minimumFrameRateDuringWarmup : 4,
minimumFrameRateAfterWarmup : 8
};
/**
* Gets the {@link FrameRateMonitor} for a given scene. If the scene does not yet have
* a {@link FrameRateMonitor}, one is created with the {@link FrameRateMonitor.defaultSettings}.
*
* @param {Scene} scene The scene for which to get the {@link FrameRateMonitor}.
* @returns {FrameRateMonitor} The scene's {@link FrameRateMonitor}.
*/
FrameRateMonitor.fromScene = function(scene) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
if (!defined(scene._frameRateMonitor) || scene._frameRateMonitor.isDestroyed()) {
scene._frameRateMonitor = new FrameRateMonitor({
scene : scene
});
}
return scene._frameRateMonitor;
};
defineProperties(FrameRateMonitor.prototype, {
/**
* Gets the {@link Scene} instance for which to monitor performance.
* @memberof FrameRateMonitor.prototype
* @type {Scene}
*/
scene : {
get : function() {
return this._scene;
}
},
/**
* Gets the event that is raised when a low frame rate is detected. The function will be passed
* the {@link Scene} instance as its first parameter and the average number of frames per second
* over the sampling window as its second parameter.
* @memberof FrameRateMonitor.prototype
* @type {Event}
*/
lowFrameRate : {
get : function() {
return this._lowFrameRate;
}
},
/**
* Gets the event that is raised when the frame rate returns to a normal level after having been low.
* The function will be passed the {@link Scene} instance as its first parameter and the average
* number of frames per second over the sampling window as its second parameter.
* @memberof FrameRateMonitor.prototype
* @type {Event}
*/
nominalFrameRate : {
get : function() {
return this._nominalFrameRate;
}
},
/**
* Gets the most recently computed average frames-per-second over the last samplingWindow
.
* This property may be undefined if the frame rate has not been computed.
* @memberof FrameRateMonitor.prototype
* @type {Number}
*/
lastFramesPerSecond : {
get : function() {
return this._lastFramesPerSecond;
}
}
});
/**
* Pauses monitoring of the frame rate. To resume monitoring, {@link FrameRateMonitor#unpause}
* must be called once for each time this function is called.
* @memberof FrameRateMonitor
*/
FrameRateMonitor.prototype.pause = function() {
++this._pauseCount;
if (this._pauseCount === 1) {
this._frameTimes.length = 0;
this._lastFramesPerSecond = undefined;
}
};
/**
* Resumes monitoring of the frame rate. If {@link FrameRateMonitor#pause} was called
* multiple times, this function must be called the same number of times in order to
* actually resume monitoring.
* @memberof FrameRateMonitor
*/
FrameRateMonitor.prototype.unpause = function() {
--this._pauseCount;
if (this._pauseCount <= 0) {
this._pauseCount = 0;
this._needsQuietPeriod = true;
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @memberof FrameRateMonitor
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see FrameRateMonitor#destroy
*/
FrameRateMonitor.prototype.isDestroyed = function() {
return false;
};
/**
* Unsubscribes this instance from all events it is listening to.
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @memberof FrameRateMonitor
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see FrameRateMonitor#isDestroyed
*/
FrameRateMonitor.prototype.destroy = function() {
this._preRenderRemoveListener();
if (defined(this._visibilityChangeRemoveListener)) {
this._visibilityChangeRemoveListener();
}
return destroyObject(this);
};
function update(monitor, time) {
if (monitor._pauseCount > 0) {
return;
}
var timeStamp = getTimestamp();
if (monitor._needsQuietPeriod) {
monitor._needsQuietPeriod = false;
monitor._frameTimes.length = 0;
monitor._quietPeriodEndTime = timeStamp + (monitor.quietPeriod / TimeConstants.SECONDS_PER_MILLISECOND);
monitor._warmupPeriodEndTime = monitor._quietPeriodEndTime + ((monitor.warmupPeriod + monitor.samplingWindow) / TimeConstants.SECONDS_PER_MILLISECOND);
} else if (timeStamp >= monitor._quietPeriodEndTime) {
monitor._frameTimes.push(timeStamp);
var beginningOfWindow = timeStamp - (monitor.samplingWindow / TimeConstants.SECONDS_PER_MILLISECOND);
if (monitor._frameTimes.length >= 2 && monitor._frameTimes[0] <= beginningOfWindow) {
while (monitor._frameTimes.length >= 2 && monitor._frameTimes[1] < beginningOfWindow) {
monitor._frameTimes.shift();
}
var averageTimeBetweenFrames = (timeStamp - monitor._frameTimes[0]) / (monitor._frameTimes.length - 1);
monitor._lastFramesPerSecond = 1000.0 / averageTimeBetweenFrames;
var maximumFrameTime = 1000.0 / (timeStamp > monitor._warmupPeriodEndTime ? monitor.minimumFrameRateAfterWarmup : monitor.minimumFrameRateDuringWarmup);
if (averageTimeBetweenFrames > maximumFrameTime) {
if (!monitor._frameRateIsLow) {
monitor._frameRateIsLow = true;
monitor._needsQuietPeriod = true;
monitor.lowFrameRate.raiseEvent(monitor.scene, monitor._lastFramesPerSecond);
}
} else if (monitor._frameRateIsLow) {
monitor._frameRateIsLow = false;
monitor._needsQuietPeriod = true;
monitor.nominalFrameRate.raiseEvent(monitor.scene, monitor._lastFramesPerSecond);
}
}
}
}
function visibilityChanged(monitor) {
if (document[monitor._hiddenPropertyName]) {
monitor.pause();
} else {
monitor.unpause();
}
}
return FrameRateMonitor;
});
/*global define*/
define('Scene/FrameState',[
'./SceneMode'
], function(
SceneMode) {
'use strict';
/**
* State information about the current frame. An instance of this class
* is provided to update functions.
*
* @param {CreditDisplay} creditDisplay Handles adding and removing credits from an HTML element
*
* @alias FrameState
* @constructor
*
* @private
*/
function FrameState(context, creditDisplay) {
/**
* The rendering context.
* @type {Context}
*/
this.context = context;
/**
* An array of rendering commands.
* @type {DrawCommand[]}
*/
this.commandList = [];
/**
* An array of shadow maps.
* @type {ShadowMap[]}
*/
this.shadowMaps = [];
/**
* The current mode of the scene.
* @type {SceneMode}
* @default {@link SceneMode.SCENE3D}
*/
this.mode = SceneMode.SCENE3D;
/**
* The current morph transition time between 2D/Columbus View and 3D,
* with 0.0 being 2D or Columbus View and 1.0 being 3D.
*
* @type {Number}
*/
this.morphTime = SceneMode.getMorphTime(SceneMode.SCENE3D);
/**
* The current frame number.
*
* @type {Number}
* @default 0
*/
this.frameNumber = 0;
/**
* The scene's current time.
*
* @type {JulianDate}
* @default undefined
*/
this.time = undefined;
/**
* The map projection to use in 2D and Columbus View modes.
*
* @type {MapProjection}
* @default undefined
*/
this.mapProjection = undefined;
/**
* The current camera.
* @type {Camera}
* @default undefined
*/
this.camera = undefined;
/**
* The culling volume.
* @type {CullingVolume}
* @default undefined
*/
this.cullingVolume = undefined;
/**
* The current occluder.
* @type {Occluder}
* @default undefined
*/
this.occluder = undefined;
this.passes = {
/**
* true
if the primitive should update for a render pass, false
otherwise.
* @type {Boolean}
* @default false
*/
render : false,
/**
* true
if the primitive should update for a picking pass, false
otherwise.
* @type {Boolean}
* @default false
*/
pick : false
};
/**
* The credit display.
* @type {CreditDisplay}
*/
this.creditDisplay = creditDisplay;
/**
* An array of functions to be called at the end of the frame. This array
* will be cleared after each frame.
*
* This allows queueing up events in update
functions and
* firing them at a time when the subscribers are free to change the
* scene state, e.g., manipulate the camera, instead of firing events
* directly in update
functions.
*
*
* @type {FrameState~AfterRenderCallback[]}
*
* @example
* frameState.afterRender.push(function() {
* // take some action, raise an event, etc.
* });
*/
this.afterRender = [];
/**
* Gets whether or not to optimized for 3D only.
* @type {Boolean}
* @default false
*/
this.scene3DOnly = false;
this.fog = {
/**
* true
if fog is enabled, false
otherwise.
* @type {Boolean}
* @default false
*/
enabled : false,
/**
* A positive number used to mix the color and fog color based on camera distance.
* @type {Number}
* @default undefined
*/
density : undefined,
/**
* A scalar used to modify the screen space error of geometry partially in fog.
* @type {Number}
* @default undefined
*/
sse : undefined
};
/**
* A scalar used to exaggerate the terrain.
* @type {Number}
*/
this.terrainExaggeration = 1.0;
this.shadowHints = {
/**
* Whether there are any active shadow maps this frame.
* @type {Boolean}
*/
shadowsEnabled : true,
/**
* All shadow maps that are enabled this frame.
*/
shadowMaps : [],
/**
* Shadow maps that originate from light sources. Does not include shadow maps that are used for
* analytical purposes. Only these shadow maps will be used to generate receive shadows shaders.
*/
lightShadowMaps : [],
/**
* The near plane of the scene's frustum commands. Used for fitting cascaded shadow maps.
* @type {Number}
*/
nearPlane : 1.0,
/**
* The far plane of the scene's frustum commands. Used for fitting cascaded shadow maps.
* @type {Number}
*/
farPlane : 5000.0,
/**
* The size of the bounding volume that is closest to the camera. This is used to place more shadow detail near the object.
* @type {Number}
*/
closestObjectSize : 1000.0,
/**
* The time when a shadow map was last dirty
* @type {Number}
*/
lastDirtyTime : 0,
/**
* Whether the shadows maps are out of view this frame
* @type {Boolean}
*/
outOfView : true
};
}
/**
* A function that will be called at the end of the frame.
* @callback FrameState~AfterRenderCallback
*/
return FrameState;
});
/*global define*/
define('Scene/FrustumCommands',[
'../Core/defaultValue',
'../Renderer/Pass'
], function(
defaultValue,
Pass) {
'use strict';
/**
* Defines a list of commands whose geometry are bound by near and far distances from the camera.
* @alias FrustumCommands
* @constructor
*
* @param {Number} [near=0.0] The lower bound or closest distance from the camera.
* @param {Number} [far=0.0] The upper bound or farthest distance from the camera.
*
* @private
*/
function FrustumCommands(near, far) {
this.near = defaultValue(near, 0.0);
this.far = defaultValue(far, 0.0);
var numPasses = Pass.NUMBER_OF_PASSES;
var commands = new Array(numPasses);
var indices = new Array(numPasses);
for (var i = 0; i < numPasses; ++i) {
commands[i] = [];
indices[i] = 0;
}
this.commands = commands;
this.indices = indices;
}
return FrustumCommands;
});
/**
* @license
* Copyright (c) 2011 NVIDIA Corporation. All rights reserved.
*
* TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
* *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
* OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA
* OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR
* CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
* OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY
* OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/FXAA',[],function() {
'use strict';
return "#ifndef FXAA_PRESET\n\
#define FXAA_PRESET 3\n\
#endif\n\
#if (FXAA_PRESET == 3)\n\
#define FXAA_EDGE_THRESHOLD (1.0/8.0)\n\
#define FXAA_EDGE_THRESHOLD_MIN (1.0/24.0)\n\
#define FXAA_SEARCH_STEPS 16\n\
#define FXAA_SEARCH_THRESHOLD (1.0/4.0)\n\
#define FXAA_SUBPIX_CAP (3.0/4.0)\n\
#define FXAA_SUBPIX_TRIM (1.0/4.0)\n\
#endif\n\
#if (FXAA_PRESET == 4)\n\
#define FXAA_EDGE_THRESHOLD (1.0/8.0)\n\
#define FXAA_EDGE_THRESHOLD_MIN (1.0/24.0)\n\
#define FXAA_SEARCH_STEPS 24\n\
#define FXAA_SEARCH_THRESHOLD (1.0/4.0)\n\
#define FXAA_SUBPIX_CAP (3.0/4.0)\n\
#define FXAA_SUBPIX_TRIM (1.0/4.0)\n\
#endif\n\
#if (FXAA_PRESET == 5)\n\
#define FXAA_EDGE_THRESHOLD (1.0/8.0)\n\
#define FXAA_EDGE_THRESHOLD_MIN (1.0/24.0)\n\
#define FXAA_SEARCH_STEPS 32\n\
#define FXAA_SEARCH_THRESHOLD (1.0/4.0)\n\
#define FXAA_SUBPIX_CAP (3.0/4.0)\n\
#define FXAA_SUBPIX_TRIM (1.0/4.0)\n\
#endif\n\
#define FXAA_SUBPIX_TRIM_SCALE (1.0/(1.0 - FXAA_SUBPIX_TRIM))\n\
float FxaaLuma(vec3 rgb) {\n\
return rgb.y * (0.587/0.299) + rgb.x;\n\
}\n\
vec3 FxaaLerp3(vec3 a, vec3 b, float amountOfA) {\n\
return (vec3(-amountOfA) * b) + ((a * vec3(amountOfA)) + b);\n\
}\n\
vec4 FxaaTexOff(sampler2D tex, vec2 pos, ivec2 off, vec2 rcpFrame) {\n\
float x = pos.x + float(off.x) * rcpFrame.x;\n\
float y = pos.y + float(off.y) * rcpFrame.y;\n\
return texture2D(tex, vec2(x, y));\n\
}\n\
vec3 FxaaPixelShader(vec2 pos, sampler2D tex, vec2 rcpFrame)\n\
{\n\
vec3 rgbN = FxaaTexOff(tex, pos.xy, ivec2( 0,-1), rcpFrame).xyz;\n\
vec3 rgbW = FxaaTexOff(tex, pos.xy, ivec2(-1, 0), rcpFrame).xyz;\n\
vec3 rgbM = FxaaTexOff(tex, pos.xy, ivec2( 0, 0), rcpFrame).xyz;\n\
vec3 rgbE = FxaaTexOff(tex, pos.xy, ivec2( 1, 0), rcpFrame).xyz;\n\
vec3 rgbS = FxaaTexOff(tex, pos.xy, ivec2( 0, 1), rcpFrame).xyz;\n\
float lumaN = FxaaLuma(rgbN);\n\
float lumaW = FxaaLuma(rgbW);\n\
float lumaM = FxaaLuma(rgbM);\n\
float lumaE = FxaaLuma(rgbE);\n\
float lumaS = FxaaLuma(rgbS);\n\
float rangeMin = min(lumaM, min(min(lumaN, lumaW), min(lumaS, lumaE)));\n\
float rangeMax = max(lumaM, max(max(lumaN, lumaW), max(lumaS, lumaE)));\n\
float range = rangeMax - rangeMin;\n\
if(range < max(FXAA_EDGE_THRESHOLD_MIN, rangeMax * FXAA_EDGE_THRESHOLD))\n\
{\n\
return rgbM;\n\
}\n\
vec3 rgbL = rgbN + rgbW + rgbM + rgbE + rgbS;\n\
float lumaL = (lumaN + lumaW + lumaE + lumaS) * 0.25;\n\
float rangeL = abs(lumaL - lumaM);\n\
float blendL = max(0.0, (rangeL / range) - FXAA_SUBPIX_TRIM) * FXAA_SUBPIX_TRIM_SCALE;\n\
blendL = min(FXAA_SUBPIX_CAP, blendL);\n\
vec3 rgbNW = FxaaTexOff(tex, pos.xy, ivec2(-1,-1), rcpFrame).xyz;\n\
vec3 rgbNE = FxaaTexOff(tex, pos.xy, ivec2( 1,-1), rcpFrame).xyz;\n\
vec3 rgbSW = FxaaTexOff(tex, pos.xy, ivec2(-1, 1), rcpFrame).xyz;\n\
vec3 rgbSE = FxaaTexOff(tex, pos.xy, ivec2( 1, 1), rcpFrame).xyz;\n\
rgbL += (rgbNW + rgbNE + rgbSW + rgbSE);\n\
rgbL *= vec3(1.0/9.0);\n\
float lumaNW = FxaaLuma(rgbNW);\n\
float lumaNE = FxaaLuma(rgbNE);\n\
float lumaSW = FxaaLuma(rgbSW);\n\
float lumaSE = FxaaLuma(rgbSE);\n\
float edgeVert =\n\
abs((0.25 * lumaNW) + (-0.5 * lumaN) + (0.25 * lumaNE)) +\n\
abs((0.50 * lumaW ) + (-1.0 * lumaM) + (0.50 * lumaE )) +\n\
abs((0.25 * lumaSW) + (-0.5 * lumaS) + (0.25 * lumaSE));\n\
float edgeHorz =\n\
abs((0.25 * lumaNW) + (-0.5 * lumaW) + (0.25 * lumaSW)) +\n\
abs((0.50 * lumaN ) + (-1.0 * lumaM) + (0.50 * lumaS )) +\n\
abs((0.25 * lumaNE) + (-0.5 * lumaE) + (0.25 * lumaSE));\n\
bool horzSpan = edgeHorz >= edgeVert;\n\
float lengthSign = horzSpan ? -rcpFrame.y : -rcpFrame.x;\n\
if(!horzSpan)\n\
{\n\
lumaN = lumaW;\n\
lumaS = lumaE;\n\
}\n\
float gradientN = abs(lumaN - lumaM);\n\
float gradientS = abs(lumaS - lumaM);\n\
lumaN = (lumaN + lumaM) * 0.5;\n\
lumaS = (lumaS + lumaM) * 0.5;\n\
if (gradientN < gradientS)\n\
{\n\
lumaN = lumaS;\n\
lumaN = lumaS;\n\
gradientN = gradientS;\n\
lengthSign *= -1.0;\n\
}\n\
vec2 posN;\n\
posN.x = pos.x + (horzSpan ? 0.0 : lengthSign * 0.5);\n\
posN.y = pos.y + (horzSpan ? lengthSign * 0.5 : 0.0);\n\
gradientN *= FXAA_SEARCH_THRESHOLD;\n\
vec2 posP = posN;\n\
vec2 offNP = horzSpan ? vec2(rcpFrame.x, 0.0) : vec2(0.0, rcpFrame.y);\n\
float lumaEndN = lumaN;\n\
float lumaEndP = lumaN;\n\
bool doneN = false;\n\
bool doneP = false;\n\
posN += offNP * vec2(-1.0, -1.0);\n\
posP += offNP * vec2( 1.0, 1.0);\n\
for(int i = 0; i < FXAA_SEARCH_STEPS; i++) {\n\
if(!doneN)\n\
{\n\
lumaEndN = FxaaLuma(texture2D(tex, posN.xy).xyz);\n\
}\n\
if(!doneP)\n\
{\n\
lumaEndP = FxaaLuma(texture2D(tex, posP.xy).xyz);\n\
}\n\
doneN = doneN || (abs(lumaEndN - lumaN) >= gradientN);\n\
doneP = doneP || (abs(lumaEndP - lumaN) >= gradientN);\n\
if(doneN && doneP)\n\
{\n\
break;\n\
}\n\
if(!doneN)\n\
{\n\
posN -= offNP;\n\
}\n\
if(!doneP)\n\
{\n\
posP += offNP;\n\
}\n\
}\n\
float dstN = horzSpan ? pos.x - posN.x : pos.y - posN.y;\n\
float dstP = horzSpan ? posP.x - pos.x : posP.y - pos.y;\n\
bool directionN = dstN < dstP;\n\
lumaEndN = directionN ? lumaEndN : lumaEndP;\n\
if(((lumaM - lumaN) < 0.0) == ((lumaEndN - lumaN) < 0.0))\n\
{\n\
lengthSign = 0.0;\n\
}\n\
float spanLength = (dstP + dstN);\n\
dstN = directionN ? dstN : dstP;\n\
float subPixelOffset = (0.5 + (dstN * (-1.0/spanLength))) * lengthSign;\n\
vec3 rgbF = texture2D(tex, vec2(\n\
pos.x + (horzSpan ? 0.0 : subPixelOffset),\n\
pos.y + (horzSpan ? subPixelOffset : 0.0))).xyz;\n\
return FxaaLerp3(rgbL, rgbF, blendL);\n\
}\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_step;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
gl_FragColor = vec4(FxaaPixelShader(v_textureCoordinates, u_texture, u_step), 1.0);\n\
}\n\
";
});
/*global define*/
define('Scene/FXAA',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/PixelFormat',
'../Renderer/ClearCommand',
'../Renderer/Framebuffer',
'../Renderer/PixelDatatype',
'../Renderer/Renderbuffer',
'../Renderer/RenderbufferFormat',
'../Renderer/RenderState',
'../Renderer/Texture',
'../Shaders/PostProcessFilters/FXAA'
], function(
BoundingRectangle,
Cartesian2,
Color,
defined,
destroyObject,
PixelFormat,
ClearCommand,
Framebuffer,
PixelDatatype,
Renderbuffer,
RenderbufferFormat,
RenderState,
Texture,
FXAAFS) {
'use strict';
/**
* @private
*/
function FXAA(context) {
this._texture = undefined;
this._depthStencilTexture = undefined;
this._depthStencilRenderbuffer = undefined;
this._fbo = undefined;
this._command = undefined;
this._viewport = new BoundingRectangle();
this._rs = undefined;
var clearCommand = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0),
depth : 1.0,
owner : this
});
this._clearCommand = clearCommand;
}
function destroyResources(fxaa) {
fxaa._fbo = fxaa._fbo && fxaa._fbo.destroy();
fxaa._texture = fxaa._texture && fxaa._texture.destroy();
fxaa._depthStencilTexture = fxaa._depthStencilTexture && fxaa._depthStencilTexture.destroy();
fxaa._depthStencilRenderbuffer = fxaa._depthStencilRenderbuffer && fxaa._depthStencilRenderbuffer.destroy();
fxaa._fbo = undefined;
fxaa._texture = undefined;
fxaa._depthStencilTexture = undefined;
fxaa._depthStencilRenderbuffer = undefined;
if (defined(fxaa._command)) {
fxaa._command.shaderProgram = fxaa._command.shaderProgram && fxaa._command.shaderProgram.destroy();
fxaa._command = undefined;
}
}
FXAA.prototype.update = function(context) {
var width = context.drawingBufferWidth;
var height = context.drawingBufferHeight;
var fxaaTexture = this._texture;
var textureChanged = !defined(fxaaTexture) || fxaaTexture.width !== width || fxaaTexture.height !== height;
if (textureChanged) {
this._texture = this._texture && this._texture.destroy();
this._depthStencilTexture = this._depthStencilTexture && this._depthStencilTexture.destroy();
this._depthStencilRenderbuffer = this._depthStencilRenderbuffer && this._depthStencilRenderbuffer.destroy();
this._texture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE
});
if (context.depthTexture) {
this._depthStencilTexture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.DEPTH_STENCIL,
pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8
});
} else {
this._depthStencilRenderbuffer = new Renderbuffer({
context : context,
width : width,
height : height,
format : RenderbufferFormat.DEPTH_STENCIL
});
}
}
if (!defined(this._fbo) || textureChanged) {
this._fbo = this._fbo && this._fbo.destroy();
this._fbo = new Framebuffer({
context : context,
colorTextures : [this._texture],
depthStencilTexture : this._depthStencilTexture,
depthStencilRenderbuffer : this._depthStencilRenderbuffer,
destroyAttachments : false
});
}
if (!defined(this._command)) {
this._command = context.createViewportQuadCommand(FXAAFS, {
owner : this
});
}
this._viewport.width = width;
this._viewport.height = height;
if (!defined(this._rs) || !BoundingRectangle.equals(this._rs.viewport, this._viewport)) {
this._rs = RenderState.fromCache({
viewport : this._viewport
});
}
this._command.renderState = this._rs;
if (textureChanged) {
var that = this;
var step = new Cartesian2(1.0 / this._texture.width, 1.0 / this._texture.height);
this._command.uniformMap = {
u_texture : function() {
return that._texture;
},
u_step : function() {
return step;
}
};
}
};
FXAA.prototype.execute = function(context, passState) {
this._command.execute(context, passState);
};
FXAA.prototype.clear = function(context, passState, clearColor) {
var framebuffer = passState.framebuffer;
passState.framebuffer = this._fbo;
Color.clone(clearColor, this._clearCommand.color);
this._clearCommand.execute(context, passState);
passState.framebuffer = framebuffer;
};
FXAA.prototype.getColorFramebuffer = function() {
return this._fbo;
};
FXAA.prototype.isDestroyed = function() {
return false;
};
FXAA.prototype.destroy = function() {
destroyResources(this);
return destroyObject(this);
};
return FXAA;
});
/*global define*/
define('Scene/GetFeatureInfoFormat',[
'../Core/Cartographic',
'../Core/defined',
'../Core/DeveloperError',
'../Core/RuntimeError',
'./ImageryLayerFeatureInfo'
], function(
Cartographic,
defined,
DeveloperError,
RuntimeError,
ImageryLayerFeatureInfo) {
'use strict';
/**
* Describes the format in which to request GetFeatureInfo from a Web Map Service (WMS) server.
*
* @alias GetFeatureInfoFormat
* @constructor
*
* @param {String} type The type of response to expect from a GetFeatureInfo request. Valid
* values are 'json', 'xml', 'html', or 'text'.
* @param {String} [format] The info format to request from the WMS server. This is usually a
* MIME type such as 'application/json' or text/xml'. If this parameter is not specified, the provider will request 'json'
* using 'application/json', 'xml' using 'text/xml', 'html' using 'text/html', and 'text' using 'text/plain'.
* @param {Function} [callback] A function to invoke with the GetFeatureInfo response from the WMS server
* in order to produce an array of picked {@link ImageryLayerFeatureInfo} instances. If this parameter is not specified,
* a default function for the type of response is used.
*/
function GetFeatureInfoFormat(type, format, callback) {
if (!defined(type)) {
throw new DeveloperError('type is required.');
}
this.type = type;
if (!defined(format)) {
if (type === 'json') {
format = 'application/json';
} else if (type === 'xml') {
format = 'text/xml';
} else if (type === 'html') {
format = 'text/html';
} else if (type === 'text') {
format = 'text/plain';
}
else {
throw new DeveloperError('format is required when type is not "json", "xml", "html", or "text".');
}
}
this.format = format;
if (!defined(callback)) {
if (type === 'json') {
callback = geoJsonToFeatureInfo;
} else if (type === 'xml') {
callback = xmlToFeatureInfo;
} else if (type === 'html') {
callback = textToFeatureInfo;
} else if (type === 'text') {
callback = textToFeatureInfo;
}
else {
throw new DeveloperError('callback is required when type is not "json", "xml", "html", or "text".');
}
}
this.callback = callback;
}
function geoJsonToFeatureInfo(json) {
var result = [];
var features = json.features;
for (var i = 0; i < features.length; ++i) {
var feature = features[i];
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.data = feature;
featureInfo.properties = feature.properties;
featureInfo.configureNameFromProperties(feature.properties);
featureInfo.configureDescriptionFromProperties(feature.properties);
// If this is a point feature, use the coordinates of the point.
if (defined(feature.geometry) && feature.geometry.type === 'Point') {
var longitude = feature.geometry.coordinates[0];
var latitude = feature.geometry.coordinates[1];
featureInfo.position = Cartographic.fromDegrees(longitude, latitude);
}
result.push(featureInfo);
}
return result;
}
var mapInfoMxpNamespace = 'http://www.mapinfo.com/mxp';
var esriWmsNamespace = 'http://www.esri.com/wms';
var wfsNamespace = 'http://www.opengis.net/wfs';
var gmlNamespace = 'http://www.opengis.net/gml';
function xmlToFeatureInfo(xml) {
var documentElement = xml.documentElement;
if (documentElement.localName === 'MultiFeatureCollection' && documentElement.namespaceURI === mapInfoMxpNamespace) {
// This looks like a MapInfo MXP response
return mapInfoXmlToFeatureInfo(xml);
} else if (documentElement.localName === 'FeatureInfoResponse' && documentElement.namespaceURI === esriWmsNamespace) {
// This looks like an Esri WMS response
return esriXmlToFeatureInfo(xml);
} else if (documentElement.localName === 'FeatureCollection' && documentElement.namespaceURI === wfsNamespace) {
// This looks like a WFS/GML response.
return gmlToFeatureInfo(xml);
} else if (documentElement.localName === 'ServiceExceptionReport') {
// This looks like a WMS server error, so no features picked.
throw new RuntimeError(new XMLSerializer().serializeToString(documentElement));
} else if (documentElement.localName === 'msGMLOutput') {
return msGmlToFeatureInfo(xml);
} else {
// Unknown response type, so just dump the XML itself into the description.
return unknownXmlToFeatureInfo(xml);
}
}
function mapInfoXmlToFeatureInfo(xml) {
var result = [];
var multiFeatureCollection = xml.documentElement;
var features = multiFeatureCollection.getElementsByTagNameNS(mapInfoMxpNamespace, 'Feature');
for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) {
var feature = features[featureIndex];
var properties = {};
var propertyElements = feature.getElementsByTagNameNS(mapInfoMxpNamespace, 'Val');
for (var propertyIndex = 0; propertyIndex < propertyElements.length; ++propertyIndex) {
var propertyElement = propertyElements[propertyIndex];
if (propertyElement.hasAttribute('ref')) {
var name = propertyElement.getAttribute('ref');
var value = propertyElement.textContent.trim();
properties[name] = value;
}
}
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.data = feature;
featureInfo.properties = properties;
featureInfo.configureNameFromProperties(properties);
featureInfo.configureDescriptionFromProperties(properties);
result.push(featureInfo);
}
return result;
}
function esriXmlToFeatureInfo(xml) {
var featureInfoResponse = xml.documentElement;
var result = [];
var properties;
var features = featureInfoResponse.getElementsByTagNameNS('*', 'FIELDS');
if (features.length > 0) {
// Standard esri format
for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) {
var feature = features[featureIndex];
properties = {};
var propertyAttributes = feature.attributes;
for (var attributeIndex = 0; attributeIndex < propertyAttributes.length; ++attributeIndex) {
var attribute = propertyAttributes[attributeIndex];
properties[attribute.name] = attribute.value;
}
result.push(imageryLayerFeatureInfoFromDataAndProperties(feature, properties));
}
} else {
// Thredds format -- looks like esri, but instead of containing FIELDS, contains FeatureInfo element
var featureInfoElements = featureInfoResponse.getElementsByTagNameNS('*', 'FeatureInfo');
for (var featureInfoElementIndex = 0; featureInfoElementIndex < featureInfoElements.length; ++featureInfoElementIndex) {
var featureInfoElement = featureInfoElements[featureInfoElementIndex];
properties = {};
// node.children is not supported in IE9-11, so use childNodes and check that child.nodeType is an element
var featureInfoChildren = featureInfoElement.childNodes;
for (var childIndex = 0; childIndex < featureInfoChildren.length; ++childIndex) {
var child = featureInfoChildren[childIndex];
if (child.nodeType === Node.ELEMENT_NODE) {
properties[child.localName] = child.textContent;
}
}
result.push(imageryLayerFeatureInfoFromDataAndProperties(featureInfoElement, properties));
}
}
return result;
}
function gmlToFeatureInfo(xml) {
var result = [];
var featureCollection = xml.documentElement;
var featureMembers = featureCollection.getElementsByTagNameNS(gmlNamespace, 'featureMember');
for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
var featureMember = featureMembers[featureIndex];
var properties = {};
getGmlPropertiesRecursively(featureMember, properties);
result.push(imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties));
}
return result;
}
// msGmlToFeatureInfo is similar to gmlToFeatureInfo, but assumes different XML structure
// eg. bar ...
function msGmlToFeatureInfo(xml) {
var result = [];
// Find the first child. Except for IE, this would work:
// var layer = xml.documentElement.children[0];
var layer;
var children = xml.documentElement.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].nodeType === Node.ELEMENT_NODE) {
layer = children[i];
break;
}
}
var featureMembers = layer.childNodes;
for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
var featureMember = featureMembers[featureIndex];
if (featureMember.nodeType === Node.ELEMENT_NODE) {
var properties = {};
getGmlPropertiesRecursively(featureMember, properties);
result.push(imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties));
}
}
return result;
}
function getGmlPropertiesRecursively(gmlNode, properties) {
var isSingleValue = true;
for (var i = 0; i < gmlNode.childNodes.length; ++i) {
var child = gmlNode.childNodes[i];
if (child.nodeType === Node.ELEMENT_NODE) {
isSingleValue = false;
}
if (child.localName === 'Point' || child.localName === 'LineString' || child.localName === 'Polygon' || child.localName === 'boundedBy') {
continue;
}
if (child.hasChildNodes() && getGmlPropertiesRecursively(child, properties)) {
properties[child.localName] = child.textContent;
}
}
return isSingleValue;
}
function imageryLayerFeatureInfoFromDataAndProperties(data, properties) {
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.data = data;
featureInfo.properties = properties;
featureInfo.configureNameFromProperties(properties);
featureInfo.configureDescriptionFromProperties(properties);
return featureInfo;
}
function unknownXmlToFeatureInfo(xml) {
var xmlText = new XMLSerializer().serializeToString(xml);
var element = document.createElement('div');
var pre = document.createElement('pre');
pre.textContent = xmlText;
element.appendChild(pre);
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.data = xml;
featureInfo.description = element.innerHTML;
return [featureInfo];
}
var emptyBodyRegex= /\s*<\/body>/im;
var wmsServiceExceptionReportRegex = //im;
var titleRegex = /([\s\S]*)<\/title>/im;
function textToFeatureInfo(text) {
// If the text is HTML and it has an empty body tag, assume it means no features were found.
if (emptyBodyRegex.test(text)) {
return undefined;
}
// If this is a WMS exception report, treat it as "no features found" rather than showing
// bogus feature info.
if (wmsServiceExceptionReportRegex.test(text)) {
return undefined;
}
// If the text has a element, use it as the name.
var name;
var title = titleRegex.exec(text);
if (title && title.length > 1) {
name = title[1];
}
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.name = name;
featureInfo.description = text;
featureInfo.data = text;
return [featureInfo];
}
return GetFeatureInfoFormat;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/GlobeFS',[],function() {
'use strict';
return "uniform vec4 u_initialColor;\n\
#if TEXTURE_UNITS > 0\n\
uniform sampler2D u_dayTextures[TEXTURE_UNITS];\n\
uniform vec4 u_dayTextureTranslationAndScale[TEXTURE_UNITS];\n\
uniform bool u_dayTextureUseWebMercatorT[TEXTURE_UNITS];\n\
#ifdef APPLY_ALPHA\n\
uniform float u_dayTextureAlpha[TEXTURE_UNITS];\n\
#endif\n\
#ifdef APPLY_BRIGHTNESS\n\
uniform float u_dayTextureBrightness[TEXTURE_UNITS];\n\
#endif\n\
#ifdef APPLY_CONTRAST\n\
uniform float u_dayTextureContrast[TEXTURE_UNITS];\n\
#endif\n\
#ifdef APPLY_HUE\n\
uniform float u_dayTextureHue[TEXTURE_UNITS];\n\
#endif\n\
#ifdef APPLY_SATURATION\n\
uniform float u_dayTextureSaturation[TEXTURE_UNITS];\n\
#endif\n\
#ifdef APPLY_GAMMA\n\
uniform float u_dayTextureOneOverGamma[TEXTURE_UNITS];\n\
#endif\n\
uniform vec4 u_dayTextureTexCoordsRectangle[TEXTURE_UNITS];\n\
#endif\n\
#ifdef SHOW_REFLECTIVE_OCEAN\n\
uniform sampler2D u_waterMask;\n\
uniform vec4 u_waterMaskTranslationAndScale;\n\
uniform float u_zoomedOutOceanSpecularIntensity;\n\
#endif\n\
#ifdef SHOW_OCEAN_WAVES\n\
uniform sampler2D u_oceanNormalMap;\n\
#endif\n\
#ifdef ENABLE_DAYNIGHT_SHADING\n\
uniform vec2 u_lightingFadeDistance;\n\
#endif\n\
varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_textureCoordinates;\n\
varying vec3 v_normalMC;\n\
varying vec3 v_normalEC;\n\
#ifdef FOG\n\
varying float v_distance;\n\
varying vec3 v_rayleighColor;\n\
varying vec3 v_mieColor;\n\
#endif\n\
vec4 sampleAndBlend(\n\
vec4 previousColor,\n\
sampler2D texture,\n\
vec2 tileTextureCoordinates,\n\
vec4 textureCoordinateRectangle,\n\
vec4 textureCoordinateTranslationAndScale,\n\
float textureAlpha,\n\
float textureBrightness,\n\
float textureContrast,\n\
float textureHue,\n\
float textureSaturation,\n\
float textureOneOverGamma)\n\
{\n\
vec2 alphaMultiplier = step(textureCoordinateRectangle.st, tileTextureCoordinates);\n\
textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y;\n\
alphaMultiplier = step(vec2(0.0), textureCoordinateRectangle.pq - tileTextureCoordinates);\n\
textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y;\n\
vec2 translation = textureCoordinateTranslationAndScale.xy;\n\
vec2 scale = textureCoordinateTranslationAndScale.zw;\n\
vec2 textureCoordinates = tileTextureCoordinates * scale + translation;\n\
vec4 value = texture2D(texture, textureCoordinates);\n\
vec3 color = value.rgb;\n\
float alpha = value.a;\n\
#ifdef APPLY_BRIGHTNESS\n\
color = mix(vec3(0.0), color, textureBrightness);\n\
#endif\n\
#ifdef APPLY_CONTRAST\n\
color = mix(vec3(0.5), color, textureContrast);\n\
#endif\n\
#ifdef APPLY_HUE\n\
color = czm_hue(color, textureHue);\n\
#endif\n\
#ifdef APPLY_SATURATION\n\
color = czm_saturation(color, textureSaturation);\n\
#endif\n\
#ifdef APPLY_GAMMA\n\
color = pow(color, vec3(textureOneOverGamma));\n\
#endif\n\
float sourceAlpha = alpha * textureAlpha;\n\
float outAlpha = mix(previousColor.a, 1.0, sourceAlpha);\n\
vec3 outColor = mix(previousColor.rgb * previousColor.a, color, sourceAlpha) / outAlpha;\n\
return vec4(outColor, outAlpha);\n\
}\n\
vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates);\n\
vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float specularMapValue);\n\
void main()\n\
{\n\
vec4 color = computeDayColor(u_initialColor, clamp(v_textureCoordinates, 0.0, 1.0));\n\
#ifdef SHOW_TILE_BOUNDARIES\n\
if (v_textureCoordinates.x < (1.0/256.0) || v_textureCoordinates.x > (255.0/256.0) ||\n\
v_textureCoordinates.y < (1.0/256.0) || v_textureCoordinates.y > (255.0/256.0))\n\
{\n\
color = vec4(1.0, 0.0, 0.0, 1.0);\n\
}\n\
#endif\n\
#if defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING)\n\
vec3 normalMC = czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0));\n\
vec3 normalEC = czm_normal3D * normalMC;\n\
#endif\n\
#ifdef SHOW_REFLECTIVE_OCEAN\n\
vec2 waterMaskTranslation = u_waterMaskTranslationAndScale.xy;\n\
vec2 waterMaskScale = u_waterMaskTranslationAndScale.zw;\n\
vec2 waterMaskTextureCoordinates = v_textureCoordinates.xy * waterMaskScale + waterMaskTranslation;\n\
float mask = texture2D(u_waterMask, waterMaskTextureCoordinates).r;\n\
if (mask > 0.0)\n\
{\n\
mat3 enuToEye = czm_eastNorthUpToEyeCoordinates(v_positionMC, normalEC);\n\
vec2 ellipsoidTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC);\n\
vec2 ellipsoidFlippedTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC.zyx);\n\
vec2 textureCoordinates = mix(ellipsoidTextureCoordinates, ellipsoidFlippedTextureCoordinates, czm_morphTime * smoothstep(0.9, 0.95, normalMC.z));\n\
color = computeWaterColor(v_positionEC, textureCoordinates, enuToEye, color, mask);\n\
}\n\
#endif\n\
#ifdef ENABLE_VERTEX_LIGHTING\n\
float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalize(v_normalEC)) * 0.9 + 0.3, 0.0, 1.0);\n\
vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a);\n\
#elif defined(ENABLE_DAYNIGHT_SHADING)\n\
float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * 5.0 + 0.3, 0.0, 1.0);\n\
float cameraDist = length(czm_view[3]);\n\
float fadeOutDist = u_lightingFadeDistance.x;\n\
float fadeInDist = u_lightingFadeDistance.y;\n\
float t = clamp((cameraDist - fadeOutDist) / (fadeInDist - fadeOutDist), 0.0, 1.0);\n\
diffuseIntensity = mix(1.0, diffuseIntensity, t);\n\
vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a);\n\
#else\n\
vec4 finalColor = color;\n\
#endif\n\
#ifdef FOG\n\
const float fExposure = 2.0;\n\
vec3 fogColor = v_mieColor + finalColor.rgb * v_rayleighColor;\n\
fogColor = vec3(1.0) - exp(-fExposure * fogColor);\n\
gl_FragColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor), finalColor.a);\n\
#else\n\
gl_FragColor = finalColor;\n\
#endif\n\
}\n\
#ifdef SHOW_REFLECTIVE_OCEAN\n\
float waveFade(float edge0, float edge1, float x)\n\
{\n\
float y = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n\
return pow(1.0 - y, 5.0);\n\
}\n\
float linearFade(float edge0, float edge1, float x)\n\
{\n\
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n\
}\n\
const float oceanFrequencyLowAltitude = 825000.0;\n\
const float oceanAnimationSpeedLowAltitude = 0.004;\n\
const float oceanOneOverAmplitudeLowAltitude = 1.0 / 2.0;\n\
const float oceanSpecularIntensity = 0.5;\n\
const float oceanFrequencyHighAltitude = 125000.0;\n\
const float oceanAnimationSpeedHighAltitude = 0.008;\n\
const float oceanOneOverAmplitudeHighAltitude = 1.0 / 2.0;\n\
vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float maskValue)\n\
{\n\
vec3 positionToEyeEC = -positionEyeCoordinates;\n\
float positionToEyeECLength = length(positionToEyeEC);\n\
vec3 normalizedpositionToEyeEC = normalize(normalize(positionToEyeEC));\n\
float waveIntensity = waveFade(70000.0, 1000000.0, positionToEyeECLength);\n\
#ifdef SHOW_OCEAN_WAVES\n\
float time = czm_frameNumber * oceanAnimationSpeedHighAltitude;\n\
vec4 noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyHighAltitude, time, 0.0);\n\
vec3 normalTangentSpaceHighAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeHighAltitude);\n\
time = czm_frameNumber * oceanAnimationSpeedLowAltitude;\n\
noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyLowAltitude, time, 0.0);\n\
vec3 normalTangentSpaceLowAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeLowAltitude);\n\
float highAltitudeFade = linearFade(0.0, 60000.0, positionToEyeECLength);\n\
float lowAltitudeFade = 1.0 - linearFade(20000.0, 60000.0, positionToEyeECLength);\n\
vec3 normalTangentSpace =\n\
(highAltitudeFade * normalTangentSpaceHighAltitude) +\n\
(lowAltitudeFade * normalTangentSpaceLowAltitude);\n\
normalTangentSpace = normalize(normalTangentSpace);\n\
normalTangentSpace.xy *= waveIntensity;\n\
normalTangentSpace = normalize(normalTangentSpace);\n\
#else\n\
vec3 normalTangentSpace = vec3(0.0, 0.0, 1.0);\n\
#endif\n\
vec3 normalEC = enuToEye * normalTangentSpace;\n\
const vec3 waveHighlightColor = vec3(0.3, 0.45, 0.6);\n\
float diffuseIntensity = czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * maskValue;\n\
vec3 diffuseHighlight = waveHighlightColor * diffuseIntensity;\n\
#ifdef SHOW_OCEAN_WAVES\n\
float tsPerturbationRatio = normalTangentSpace.z;\n\
vec3 nonDiffuseHighlight = mix(waveHighlightColor * 5.0 * (1.0 - tsPerturbationRatio), vec3(0.0), diffuseIntensity);\n\
#else\n\
vec3 nonDiffuseHighlight = vec3(0.0);\n\
#endif\n\
float specularIntensity = czm_getSpecular(czm_sunDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0) + 0.25 * czm_getSpecular(czm_moonDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0);\n\
float surfaceReflectance = mix(0.0, mix(u_zoomedOutOceanSpecularIntensity, oceanSpecularIntensity, waveIntensity), maskValue);\n\
float specular = specularIntensity * surfaceReflectance;\n\
return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a);\n\
}\n\
#endif // #ifdef SHOW_REFLECTIVE_OCEAN\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/GlobeVS',[],function() {
'use strict';
return "#ifdef QUANTIZATION_BITS12\n\
attribute vec4 compressed0;\n\
attribute float compressed1;\n\
#else\n\
attribute vec4 position3DAndHeight;\n\
attribute vec4 textureCoordAndEncodedNormals;\n\
#endif\n\
uniform vec3 u_center3D;\n\
uniform mat4 u_modifiedModelView;\n\
uniform mat4 u_modifiedModelViewProjection;\n\
uniform vec4 u_tileRectangle;\n\
uniform vec2 u_southAndNorthLatitude;\n\
uniform vec2 u_southMercatorYAndOneOverHeight;\n\
varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_textureCoordinates;\n\
varying vec3 v_normalMC;\n\
varying vec3 v_normalEC;\n\
#ifdef FOG\n\
varying float v_distance;\n\
varying vec3 v_mieColor;\n\
varying vec3 v_rayleighColor;\n\
#endif\n\
vec4 getPosition(vec3 position, float height, vec2 textureCoordinates);\n\
float get2DYPositionFraction(vec2 textureCoordinates);\n\
vec4 getPosition3DMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
return u_modifiedModelViewProjection * vec4(position, 1.0);\n\
}\n\
float get2DMercatorYPositionFraction(vec2 textureCoordinates)\n\
{\n\
const float maxTileWidth = 0.003068;\n\
float positionFraction = textureCoordinates.y;\n\
float southLatitude = u_southAndNorthLatitude.x;\n\
float northLatitude = u_southAndNorthLatitude.y;\n\
if (northLatitude - southLatitude > maxTileWidth)\n\
{\n\
float southMercatorY = u_southMercatorYAndOneOverHeight.x;\n\
float oneOverMercatorHeight = u_southMercatorYAndOneOverHeight.y;\n\
float currentLatitude = mix(southLatitude, northLatitude, textureCoordinates.y);\n\
currentLatitude = clamp(currentLatitude, -czm_webMercatorMaxLatitude, czm_webMercatorMaxLatitude);\n\
positionFraction = czm_latitudeToWebMercatorFraction(currentLatitude, southMercatorY, oneOverMercatorHeight);\n\
}\n\
return positionFraction;\n\
}\n\
float get2DGeographicYPositionFraction(vec2 textureCoordinates)\n\
{\n\
return textureCoordinates.y;\n\
}\n\
vec4 getPositionPlanarEarth(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
float yPositionFraction = get2DYPositionFraction(textureCoordinates);\n\
vec4 rtcPosition2D = vec4(height, mix(u_tileRectangle.st, u_tileRectangle.pq, vec2(textureCoordinates.x, yPositionFraction)), 1.0);\n\
return u_modifiedModelViewProjection * rtcPosition2D;\n\
}\n\
vec4 getPosition2DMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
return getPositionPlanarEarth(position, 0.0, textureCoordinates);\n\
}\n\
vec4 getPositionColumbusViewMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
return getPositionPlanarEarth(position, height, textureCoordinates);\n\
}\n\
vec4 getPositionMorphingMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
vec3 position3DWC = position + u_center3D;\n\
float yPositionFraction = get2DYPositionFraction(textureCoordinates);\n\
vec4 position2DWC = vec4(height, mix(u_tileRectangle.st, u_tileRectangle.pq, vec2(textureCoordinates.x, yPositionFraction)), 1.0);\n\
vec4 morphPosition = czm_columbusViewMorph(position2DWC, vec4(position3DWC, 1.0), czm_morphTime);\n\
return czm_modelViewProjection * morphPosition;\n\
}\n\
#ifdef QUANTIZATION_BITS12\n\
uniform vec2 u_minMaxHeight;\n\
uniform mat4 u_scaleAndBias;\n\
#endif\n\
void main()\n\
{\n\
#ifdef QUANTIZATION_BITS12\n\
vec2 xy = czm_decompressTextureCoordinates(compressed0.x);\n\
vec2 zh = czm_decompressTextureCoordinates(compressed0.y);\n\
vec3 position = vec3(xy, zh.x);\n\
float height = zh.y;\n\
vec2 textureCoordinates = czm_decompressTextureCoordinates(compressed0.z);\n\
height = height * (u_minMaxHeight.y - u_minMaxHeight.x) + u_minMaxHeight.x;\n\
position = (u_scaleAndBias * vec4(position, 1.0)).xyz;\n\
#if (defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)) && defined(INCLUDE_WEB_MERCATOR_Y)\n\
float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;\n\
float encodedNormal = compressed1;\n\
#elif defined(INCLUDE_WEB_MERCATOR_Y)\n\
float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;\n\
float encodedNormal = 0.0;\n\
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)\n\
float webMercatorT = textureCoordinates.y;\n\
float encodedNormal = compressed0.w;\n\
#else\n\
float webMercatorT = textureCoordinates.y;\n\
float encodedNormal = 0.0;\n\
#endif\n\
#else\n\
vec3 position = position3DAndHeight.xyz;\n\
float height = position3DAndHeight.w;\n\
vec2 textureCoordinates = textureCoordAndEncodedNormals.xy;\n\
#if (defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)) && defined(INCLUDE_WEB_MERCATOR_Y)\n\
float webMercatorT = textureCoordAndEncodedNormals.z;\n\
float encodedNormal = textureCoordAndEncodedNormals.w;\n\
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)\n\
float webMercatorT = textureCoordinates.y;\n\
float encodedNormal = textureCoordAndEncodedNormals.z;\n\
#elif defined(INCLUDE_WEB_MERCATOR_Y)\n\
float webMercatorT = textureCoordAndEncodedNormals.z;\n\
float encodedNormal = 0.0;\n\
#else\n\
float webMercatorT = textureCoordinates.y;\n\
float encodedNormal = 0.0;\n\
#endif\n\
#endif\n\
vec3 position3DWC = position + u_center3D;\n\
gl_Position = getPosition(position, height, textureCoordinates);\n\
v_textureCoordinates = vec3(textureCoordinates, webMercatorT);\n\
#if defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)\n\
v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz;\n\
v_positionMC = position3DWC;\n\
v_normalMC = czm_octDecode(encodedNormal);\n\
v_normalEC = czm_normal3D * v_normalMC;\n\
#elif defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) || defined(GENERATE_POSITION)\n\
v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz;\n\
v_positionMC = position3DWC;\n\
#endif\n\
#ifdef FOG\n\
AtmosphereColor atmosColor = computeGroundAtmosphereFromSpace(position3DWC);\n\
v_mieColor = atmosColor.mie;\n\
v_rayleighColor = atmosColor.rayleigh;\n\
v_distance = length((czm_modelView3D * vec4(position3DWC, 1.0)).xyz);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/GroundAtmosphere',[],function() {
'use strict';
return "const float fInnerRadius = 6378137.0;\n\
const float fOuterRadius = 6378137.0 * 1.025;\n\
const float fOuterRadius2 = fOuterRadius * fOuterRadius;\n\
const float Kr = 0.0025;\n\
const float Km = 0.0015;\n\
const float ESun = 15.0;\n\
const float fKrESun = Kr * ESun;\n\
const float fKmESun = Km * ESun;\n\
const float fKr4PI = Kr * 4.0 * czm_pi;\n\
const float fKm4PI = Km * 4.0 * czm_pi;\n\
const float fScale = 1.0 / (fOuterRadius - fInnerRadius);\n\
const float fScaleDepth = 0.25;\n\
const float fScaleOverScaleDepth = fScale / fScaleDepth;\n\
struct AtmosphereColor\n\
{\n\
vec3 mie;\n\
vec3 rayleigh;\n\
};\n\
const int nSamples = 2;\n\
const float fSamples = 2.0;\n\
float scale(float fCos)\n\
{\n\
float x = 1.0 - fCos;\n\
return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n\
}\n\
AtmosphereColor computeGroundAtmosphereFromSpace(vec3 v3Pos)\n\
{\n\
vec3 v3InvWavelength = vec3(1.0 / pow(0.650, 4.0), 1.0 / pow(0.570, 4.0), 1.0 / pow(0.475, 4.0));\n\
vec3 v3Ray = v3Pos - czm_viewerPositionWC;\n\
float fFar = length(v3Ray);\n\
v3Ray /= fFar;\n\
float fCameraHeight = length(czm_viewerPositionWC);\n\
float fCameraHeight2 = fCameraHeight * fCameraHeight;\n\
float B = 2.0 * length(czm_viewerPositionWC) * dot(normalize(czm_viewerPositionWC), v3Ray);\n\
float C = fCameraHeight2 - fOuterRadius2;\n\
float fDet = max(0.0, B*B - 4.0 * C);\n\
float fNear = 0.5 * (-B - sqrt(fDet));\n\
vec3 v3Start = czm_viewerPositionWC + v3Ray * fNear;\n\
fFar -= fNear;\n\
float fDepth = exp((fInnerRadius - fOuterRadius) / fScaleDepth);\n\
float fLightAngle = 1.0;\n\
float fCameraAngle = dot(-v3Ray, v3Pos) / length(v3Pos);\n\
float fCameraScale = scale(fCameraAngle);\n\
float fLightScale = scale(fLightAngle);\n\
float fCameraOffset = fDepth*fCameraScale;\n\
float fTemp = (fLightScale + fCameraScale);\n\
float fSampleLength = fFar / fSamples;\n\
float fScaledLength = fSampleLength * fScale;\n\
vec3 v3SampleRay = v3Ray * fSampleLength;\n\
vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5;\n\
vec3 v3FrontColor = vec3(0.0);\n\
vec3 v3Attenuate = vec3(0.0);\n\
for(int i=0; i 0.0) {
result += distanceToWestPlane * distanceToWestPlane;
} else if (distanceToEastPlane > 0.0) {
result += distanceToEastPlane * distanceToEastPlane;
}
if (distanceToSouthPlane > 0.0) {
result += distanceToSouthPlane * distanceToSouthPlane;
} else if (distanceToNorthPlane > 0.0) {
result += distanceToNorthPlane * distanceToNorthPlane;
}
}
var cameraHeight;
if (frameState.mode === SceneMode.SCENE3D) {
cameraHeight = cameraCartographicPosition.height;
} else {
cameraHeight = cameraCartesianPosition.x;
}
var maximumHeight = frameState.mode === SceneMode.SCENE3D ? this.maximumHeight : 0.0;
var distanceFromTop = cameraHeight - maximumHeight;
if (distanceFromTop > 0.0) {
result += distanceFromTop * distanceFromTop;
}
return Math.sqrt(result);
};
return TileBoundingBox;
});
/*global define*/
define('Scene/TileTerrain',[
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/defined',
'../Core/DeveloperError',
'../Core/IndexDatatype',
'../Core/OrientedBoundingBox',
'../Core/TileProviderError',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/VertexArray',
'../ThirdParty/when',
'./TerrainState',
'./TileBoundingBox'
], function(
BoundingSphere,
Cartesian3,
defined,
DeveloperError,
IndexDatatype,
OrientedBoundingBox,
TileProviderError,
Buffer,
BufferUsage,
VertexArray,
when,
TerrainState,
TileBoundingBox) {
'use strict';
/**
* Manages details of the terrain load or upsample process.
*
* @alias TileTerrain
* @constructor
* @private
*
* @param {TerrainData} [upsampleDetails.data] The terrain data being upsampled.
* @param {Number} [upsampleDetails.x] The X coordinate of the tile being upsampled.
* @param {Number} [upsampleDetails.y] The Y coordinate of the tile being upsampled.
* @param {Number} [upsampleDetails.level] The level coordinate of the tile being upsampled.
*/
function TileTerrain(upsampleDetails) {
/**
* The current state of the terrain in the terrain processing pipeline.
* @type {TerrainState}
* @default {@link TerrainState.UNLOADED}
*/
this.state = TerrainState.UNLOADED;
this.data = undefined;
this.mesh = undefined;
this.vertexArray = undefined;
this.upsampleDetails = upsampleDetails;
}
TileTerrain.prototype.freeResources = function() {
this.state = TerrainState.UNLOADED;
this.data = undefined;
this.mesh = undefined;
if (defined(this.vertexArray)) {
var indexBuffer = this.vertexArray.indexBuffer;
this.vertexArray.destroy();
this.vertexArray = undefined;
if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
--indexBuffer.referenceCount;
if (indexBuffer.referenceCount === 0) {
indexBuffer.destroy();
}
}
}
};
TileTerrain.prototype.publishToTile = function(tile) {
var surfaceTile = tile.data;
var mesh = this.mesh;
Cartesian3.clone(mesh.center, surfaceTile.center);
surfaceTile.minimumHeight = mesh.minimumHeight;
surfaceTile.maximumHeight = mesh.maximumHeight;
surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D);
surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox);
surfaceTile.tileBoundingBox = new TileBoundingBox({
rectangle : tile.rectangle,
minimumHeight : mesh.minimumHeight,
maximumHeight : mesh.maximumHeight,
ellipsoid : tile.tilingScheme.ellipsoid
});
tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace);
};
TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level) {
if (this.state === TerrainState.UNLOADED) {
requestTileGeometry(this, terrainProvider, x, y, level);
}
if (this.state === TerrainState.RECEIVED) {
transform(this, frameState, terrainProvider, x, y, level);
}
if (this.state === TerrainState.TRANSFORMED) {
createResources(this, frameState.context, terrainProvider, x, y, level);
}
};
function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) {
function success(terrainData) {
tileTerrain.data = terrainData;
tileTerrain.state = TerrainState.RECEIVED;
}
function failure() {
// Initially assume failure. handleError may retry, in which case the state will
// change to RECEIVING or UNLOADED.
tileTerrain.state = TerrainState.FAILED;
var message = 'Failed to obtain terrain tile X: ' + x + ' Y: ' + y + ' Level: ' + level + '.';
terrainProvider._requestError = TileProviderError.handleError(
terrainProvider._requestError,
terrainProvider,
terrainProvider.errorEvent,
message,
x, y, level,
doRequest);
}
function doRequest() {
// Request the terrain from the terrain provider.
tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level);
// If the request method returns undefined (instead of a promise), the request
// has been deferred.
if (defined(tileTerrain.data)) {
tileTerrain.state = TerrainState.RECEIVING;
when(tileTerrain.data, success, failure);
} else {
// Deferred - try again later.
tileTerrain.state = TerrainState.UNLOADED;
}
}
doRequest();
}
TileTerrain.prototype.processUpsampleStateMachine = function(frameState, terrainProvider, x, y, level) {
if (this.state === TerrainState.UNLOADED) {
var upsampleDetails = this.upsampleDetails;
if (!defined(upsampleDetails)) {
throw new DeveloperError('TileTerrain cannot upsample unless provided upsampleDetails.');
}
var sourceData = upsampleDetails.data;
var sourceX = upsampleDetails.x;
var sourceY = upsampleDetails.y;
var sourceLevel = upsampleDetails.level;
this.data = sourceData.upsample(terrainProvider.tilingScheme, sourceX, sourceY, sourceLevel, x, y, level);
if (!defined(this.data)) {
// The upsample request has been deferred - try again later.
return;
}
this.state = TerrainState.RECEIVING;
var that = this;
when(this.data, function(terrainData) {
that.data = terrainData;
that.state = TerrainState.RECEIVED;
}, function() {
that.state = TerrainState.FAILED;
});
}
if (this.state === TerrainState.RECEIVED) {
transform(this, frameState, terrainProvider, x, y, level);
}
if (this.state === TerrainState.TRANSFORMED) {
createResources(this, frameState.context, terrainProvider, x, y, level);
}
};
function transform(tileTerrain, frameState, terrainProvider, x, y, level) {
var tilingScheme = terrainProvider.tilingScheme;
var terrainData = tileTerrain.data;
var meshPromise = terrainData.createMesh(tilingScheme, x, y, level, frameState.terrainExaggeration);
if (!defined(meshPromise)) {
// Postponed.
return;
}
tileTerrain.state = TerrainState.TRANSFORMING;
when(meshPromise, function(mesh) {
tileTerrain.mesh = mesh;
tileTerrain.state = TerrainState.TRANSFORMED;
}, function() {
tileTerrain.state = TerrainState.FAILED;
});
}
function createResources(tileTerrain, context, terrainProvider, x, y, level) {
var typedArray = tileTerrain.mesh.vertices;
var buffer = Buffer.createVertexBuffer({
context : context,
typedArray : typedArray,
usage : BufferUsage.STATIC_DRAW
});
var attributes = tileTerrain.mesh.encoding.getAttributes(buffer);
var indexBuffers = tileTerrain.mesh.indices.indexBuffers || {};
var indexBuffer = indexBuffers[context.id];
if (!defined(indexBuffer) || indexBuffer.isDestroyed()) {
var indices = tileTerrain.mesh.indices;
var indexDatatype = (indices.BYTES_PER_ELEMENT === 2) ? IndexDatatype.UNSIGNED_SHORT : IndexDatatype.UNSIGNED_INT;
indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : indices,
usage : BufferUsage.STATIC_DRAW,
indexDatatype : indexDatatype
});
indexBuffer.vertexArrayDestroyable = false;
indexBuffer.referenceCount = 1;
indexBuffers[context.id] = indexBuffer;
tileTerrain.mesh.indices.indexBuffers = indexBuffers;
} else {
++indexBuffer.referenceCount;
}
tileTerrain.vertexArray = new VertexArray({
context : context,
attributes : attributes,
indexBuffer : indexBuffer
});
tileTerrain.state = TerrainState.READY;
}
return TileTerrain;
});
/*global define*/
define('Scene/GlobeSurfaceTile',[
'../Core/BoundingSphere',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/IntersectionTests',
'../Core/PixelFormat',
'../Renderer/PixelDatatype',
'../Renderer/Sampler',
'../Renderer/Texture',
'../Renderer/TextureMagnificationFilter',
'../Renderer/TextureMinificationFilter',
'../Renderer/TextureWrap',
'./ImageryState',
'./QuadtreeTileLoadState',
'./SceneMode',
'./TerrainState',
'./TileTerrain'
], function(
BoundingSphere,
Cartesian3,
Cartesian4,
defaultValue,
defined,
defineProperties,
IntersectionTests,
PixelFormat,
PixelDatatype,
Sampler,
Texture,
TextureMagnificationFilter,
TextureMinificationFilter,
TextureWrap,
ImageryState,
QuadtreeTileLoadState,
SceneMode,
TerrainState,
TileTerrain) {
'use strict';
/**
* Contains additional information about a {@link QuadtreeTile} of the globe's surface, and
* encapsulates state transition logic for loading tiles.
*
* @constructor
* @alias GlobeSurfaceTile
* @private
*/
function GlobeSurfaceTile() {
/**
* The {@link TileImagery} attached to this tile.
* @type {TileImagery[]}
* @default []
*/
this.imagery = [];
this.waterMaskTexture = undefined;
this.waterMaskTranslationAndScale = new Cartesian4(0.0, 0.0, 1.0, 1.0);
this.terrainData = undefined;
this.center = new Cartesian3();
this.vertexArray = undefined;
this.minimumHeight = 0.0;
this.maximumHeight = 0.0;
this.boundingSphere3D = new BoundingSphere();
this.boundingSphere2D = new BoundingSphere();
this.orientedBoundingBox = undefined;
this.tileBoundingBox = undefined;
this.occludeePointInScaledSpace = new Cartesian3();
this.loadedTerrain = undefined;
this.upsampledTerrain = undefined;
this.pickBoundingSphere = new BoundingSphere();
this.pickTerrain = undefined;
this.surfaceShader = undefined;
}
defineProperties(GlobeSurfaceTile.prototype, {
/**
* Gets a value indicating whether or not this tile is eligible to be unloaded.
* Typically, a tile is ineligible to be unloaded while an asynchronous operation,
* such as a request for data, is in progress on it. A tile will never be
* unloaded while it is needed for rendering, regardless of the value of this
* property.
* @memberof GlobeSurfaceTile.prototype
* @type {Boolean}
*/
eligibleForUnloading : {
get : function() {
// Do not remove tiles that are transitioning or that have
// imagery that is transitioning.
var loadedTerrain = this.loadedTerrain;
var loadingIsTransitioning = defined(loadedTerrain) &&
(loadedTerrain.state === TerrainState.RECEIVING || loadedTerrain.state === TerrainState.TRANSFORMING);
var upsampledTerrain = this.upsampledTerrain;
var upsamplingIsTransitioning = defined(upsampledTerrain) &&
(upsampledTerrain.state === TerrainState.RECEIVING || upsampledTerrain.state === TerrainState.TRANSFORMING);
var shouldRemoveTile = !loadingIsTransitioning && !upsamplingIsTransitioning;
var imagery = this.imagery;
for (var i = 0, len = imagery.length; shouldRemoveTile && i < len; ++i) {
var tileImagery = imagery[i];
shouldRemoveTile = !defined(tileImagery.loadingImagery) || tileImagery.loadingImagery.state !== ImageryState.TRANSITIONING;
}
return shouldRemoveTile;
}
}
});
function getPosition(encoding, mode, projection, vertices, index, result) {
encoding.decodePosition(vertices, index, result);
if (defined(mode) && mode !== SceneMode.SCENE3D) {
var ellipsoid = projection.ellipsoid;
var positionCart = ellipsoid.cartesianToCartographic(result);
projection.project(positionCart, result);
Cartesian3.fromElements(result.z, result.x, result.y, result);
}
return result;
}
var scratchV0 = new Cartesian3();
var scratchV1 = new Cartesian3();
var scratchV2 = new Cartesian3();
var scratchResult = new Cartesian3();
GlobeSurfaceTile.prototype.pick = function(ray, mode, projection, cullBackFaces, result) {
var terrain = this.pickTerrain;
if (!defined(terrain)) {
return undefined;
}
var mesh = terrain.mesh;
if (!defined(mesh)) {
return undefined;
}
var vertices = mesh.vertices;
var indices = mesh.indices;
var encoding = mesh.encoding;
var length = indices.length;
for (var i = 0; i < length; i += 3) {
var i0 = indices[i];
var i1 = indices[i + 1];
var i2 = indices[i + 2];
var v0 = getPosition(encoding, mode, projection, vertices, i0, scratchV0);
var v1 = getPosition(encoding, mode, projection, vertices, i1, scratchV1);
var v2 = getPosition(encoding, mode, projection, vertices, i2, scratchV2);
var intersection = IntersectionTests.rayTriangle(ray, v0, v1, v2, cullBackFaces, scratchResult);
if (defined(intersection)) {
return Cartesian3.clone(intersection, result);
}
}
return undefined;
};
GlobeSurfaceTile.prototype.freeResources = function() {
if (defined(this.waterMaskTexture)) {
--this.waterMaskTexture.referenceCount;
if (this.waterMaskTexture.referenceCount === 0) {
this.waterMaskTexture.destroy();
}
this.waterMaskTexture = undefined;
}
this.terrainData = undefined;
if (defined(this.loadedTerrain)) {
this.loadedTerrain.freeResources();
this.loadedTerrain = undefined;
}
if (defined(this.upsampledTerrain)) {
this.upsampledTerrain.freeResources();
this.upsampledTerrain = undefined;
}
if (defined(this.pickTerrain)) {
this.pickTerrain.freeResources();
this.pickTerrain = undefined;
}
var i, len;
var imageryList = this.imagery;
for (i = 0, len = imageryList.length; i < len; ++i) {
imageryList[i].freeResources();
}
this.imagery.length = 0;
this.freeVertexArray();
};
GlobeSurfaceTile.prototype.freeVertexArray = function() {
var indexBuffer;
if (defined(this.vertexArray)) {
indexBuffer = this.vertexArray.indexBuffer;
this.vertexArray = this.vertexArray.destroy();
if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
--indexBuffer.referenceCount;
if (indexBuffer.referenceCount === 0) {
indexBuffer.destroy();
}
}
}
if (defined(this.wireframeVertexArray)) {
indexBuffer = this.wireframeVertexArray.indexBuffer;
this.wireframeVertexArray = this.wireframeVertexArray.destroy();
if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
--indexBuffer.referenceCount;
if (indexBuffer.referenceCount === 0) {
indexBuffer.destroy();
}
}
}
};
GlobeSurfaceTile.processStateMachine = function(tile, frameState, terrainProvider, imageryLayerCollection, vertexArraysToDestroy) {
var surfaceTile = tile.data;
if (!defined(surfaceTile)) {
surfaceTile = tile.data = new GlobeSurfaceTile();
}
if (tile.state === QuadtreeTileLoadState.START) {
prepareNewTile(tile, terrainProvider, imageryLayerCollection);
tile.state = QuadtreeTileLoadState.LOADING;
}
if (tile.state === QuadtreeTileLoadState.LOADING) {
processTerrainStateMachine(tile, frameState, terrainProvider, vertexArraysToDestroy);
}
// The terrain is renderable as soon as we have a valid vertex array.
var isRenderable = defined(surfaceTile.vertexArray);
// But it's not done loading until our two state machines are terminated.
var isDoneLoading = !defined(surfaceTile.loadedTerrain) && !defined(surfaceTile.upsampledTerrain);
// If this tile's terrain and imagery are just upsampled from its parent, mark the tile as
// upsampled only. We won't refine a tile if its four children are upsampled only.
var isUpsampledOnly = defined(surfaceTile.terrainData) && surfaceTile.terrainData.wasCreatedByUpsampling();
// Transition imagery states
var tileImageryCollection = surfaceTile.imagery;
for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
var tileImagery = tileImageryCollection[i];
if (!defined(tileImagery.loadingImagery)) {
isUpsampledOnly = false;
continue;
}
if (tileImagery.loadingImagery.state === ImageryState.PLACEHOLDER) {
var imageryLayer = tileImagery.loadingImagery.imageryLayer;
if (imageryLayer.imageryProvider.ready) {
// Remove the placeholder and add the actual skeletons (if any)
// at the same position. Then continue the loop at the same index.
tileImagery.freeResources();
tileImageryCollection.splice(i, 1);
imageryLayer._createTileImagerySkeletons(tile, terrainProvider, i);
--i;
len = tileImageryCollection.length;
continue;
} else {
isUpsampledOnly = false;
}
}
var thisTileDoneLoading = tileImagery.processStateMachine(tile, frameState);
isDoneLoading = isDoneLoading && thisTileDoneLoading;
// The imagery is renderable as soon as we have any renderable imagery for this region.
isRenderable = isRenderable && (thisTileDoneLoading || defined(tileImagery.readyImagery));
isUpsampledOnly = isUpsampledOnly && defined(tileImagery.loadingImagery) &&
(tileImagery.loadingImagery.state === ImageryState.FAILED || tileImagery.loadingImagery.state === ImageryState.INVALID);
}
tile.upsampledFromParent = isUpsampledOnly;
// The tile becomes renderable when the terrain and all imagery data are loaded.
if (i === len) {
if (isRenderable) {
tile.renderable = true;
}
if (isDoneLoading) {
tile.state = QuadtreeTileLoadState.DONE;
}
}
};
function prepareNewTile(tile, terrainProvider, imageryLayerCollection) {
var surfaceTile = tile.data;
var upsampleTileDetails = getUpsampleTileDetails(tile);
if (defined(upsampleTileDetails)) {
surfaceTile.upsampledTerrain = new TileTerrain(upsampleTileDetails);
}
if (isDataAvailable(tile, terrainProvider)) {
surfaceTile.loadedTerrain = new TileTerrain();
}
// Map imagery tiles to this terrain tile
for (var i = 0, len = imageryLayerCollection.length; i < len; ++i) {
var layer = imageryLayerCollection.get(i);
if (layer.show) {
layer._createTileImagerySkeletons(tile, terrainProvider);
}
}
}
function processTerrainStateMachine(tile, frameState, terrainProvider, vertexArraysToDestroy) {
var surfaceTile = tile.data;
var loaded = surfaceTile.loadedTerrain;
var upsampled = surfaceTile.upsampledTerrain;
var suspendUpsampling = false;
if (defined(loaded)) {
loaded.processLoadStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level);
// Publish the terrain data on the tile as soon as it is available.
// We'll potentially need it to upsample child tiles.
if (loaded.state >= TerrainState.RECEIVED) {
if (surfaceTile.terrainData !== loaded.data) {
surfaceTile.terrainData = loaded.data;
// If there's a water mask included in the terrain data, create a
// texture for it.
createWaterMaskTextureIfNeeded(frameState.context, surfaceTile);
propagateNewLoadedDataToChildren(tile);
}
suspendUpsampling = true;
}
if (loaded.state === TerrainState.READY) {
loaded.publishToTile(tile);
if (defined(tile.data.vertexArray)) {
// Free the tiles existing vertex array on next render.
vertexArraysToDestroy.push(tile.data.vertexArray);
}
// Transfer ownership of the vertex array to the tile itself.
tile.data.vertexArray = loaded.vertexArray;
loaded.vertexArray = undefined;
// No further loading or upsampling is necessary.
surfaceTile.pickTerrain = defaultValue(surfaceTile.loadedTerrain, surfaceTile.upsampledTerrain);
surfaceTile.loadedTerrain = undefined;
surfaceTile.upsampledTerrain = undefined;
} else if (loaded.state === TerrainState.FAILED) {
// Loading failed for some reason, or data is simply not available,
// so no need to continue trying to load. Any retrying will happen before we
// reach this point.
surfaceTile.loadedTerrain = undefined;
}
}
if (!suspendUpsampling && defined(upsampled)) {
upsampled.processUpsampleStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level);
// Publish the terrain data on the tile as soon as it is available.
// We'll potentially need it to upsample child tiles.
// It's safe to overwrite terrainData because we won't get here after
// loaded terrain data has been received.
if (upsampled.state >= TerrainState.RECEIVED) {
if (surfaceTile.terrainData !== upsampled.data) {
surfaceTile.terrainData = upsampled.data;
// If the terrain provider has a water mask, "upsample" that as well
// by computing texture translation and scale.
if (terrainProvider.hasWaterMask) {
upsampleWaterMask(tile);
}
propagateNewUpsampledDataToChildren(tile);
}
}
if (upsampled.state === TerrainState.READY) {
upsampled.publishToTile(tile);
if (defined(tile.data.vertexArray)) {
// Free the tiles existing vertex array on next render.
vertexArraysToDestroy.push(tile.data.vertexArray);
}
// Transfer ownership of the vertex array to the tile itself.
tile.data.vertexArray = upsampled.vertexArray;
upsampled.vertexArray = undefined;
// No further upsampling is necessary. We need to continue loading, though.
surfaceTile.pickTerrain = surfaceTile.upsampledTerrain;
surfaceTile.upsampledTerrain = undefined;
} else if (upsampled.state === TerrainState.FAILED) {
// Upsampling failed for some reason. This is pretty much a catastrophic failure,
// but maybe we'll be saved by loading.
surfaceTile.upsampledTerrain = undefined;
}
}
}
function getUpsampleTileDetails(tile) {
// Find the nearest ancestor with loaded terrain.
var sourceTile = tile.parent;
while (defined(sourceTile) && defined(sourceTile.data) && !defined(sourceTile.data.terrainData)) {
sourceTile = sourceTile.parent;
}
if (!defined(sourceTile) || !defined(sourceTile.data)) {
// No ancestors have loaded terrain - try again later.
return undefined;
}
return {
data : sourceTile.data.terrainData,
x : sourceTile.x,
y : sourceTile.y,
level : sourceTile.level
};
}
function propagateNewUpsampledDataToChildren(tile) {
// Now that there's new data for this tile:
// - child tiles that were previously upsampled need to be re-upsampled based on the new data.
// Generally this is only necessary when a child tile is upsampled, and then one
// of its ancestors receives new (better) data and we want to re-upsample from the
// new data.
propagateNewUpsampledDataToChild(tile, tile._southwestChild);
propagateNewUpsampledDataToChild(tile, tile._southeastChild);
propagateNewUpsampledDataToChild(tile, tile._northwestChild);
propagateNewUpsampledDataToChild(tile, tile._northeastChild);
}
function propagateNewUpsampledDataToChild(tile, childTile) {
if (defined(childTile) && childTile.state !== QuadtreeTileLoadState.START) {
var childSurfaceTile = childTile.data;
if (defined(childSurfaceTile.terrainData) && !childSurfaceTile.terrainData.wasCreatedByUpsampling()) {
// Data for the child tile has already been loaded.
return;
}
// Restart the upsampling process, no matter its current state.
// We create a new instance rather than just restarting the existing one
// because there could be an asynchronous operation pending on the existing one.
if (defined(childSurfaceTile.upsampledTerrain)) {
childSurfaceTile.upsampledTerrain.freeResources();
}
childSurfaceTile.upsampledTerrain = new TileTerrain({
data : tile.data.terrainData,
x : tile.x,
y : tile.y,
level : tile.level
});
childTile.state = QuadtreeTileLoadState.LOADING;
}
}
function propagateNewLoadedDataToChildren(tile) {
var surfaceTile = tile.data;
// Now that there's new data for this tile:
// - child tiles that were previously upsampled need to be re-upsampled based on the new data.
// - child tiles that were previously deemed unavailable may now be available.
propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.southwestChild);
propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.southeastChild);
propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.northwestChild);
propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.northeastChild);
}
function propagateNewLoadedDataToChildTile(tile, surfaceTile, childTile) {
if (childTile.state !== QuadtreeTileLoadState.START) {
var childSurfaceTile = childTile.data;
if (defined(childSurfaceTile.terrainData) && !childSurfaceTile.terrainData.wasCreatedByUpsampling()) {
// Data for the child tile has already been loaded.
return;
}
// Restart the upsampling process, no matter its current state.
// We create a new instance rather than just restarting the existing one
// because there could be an asynchronous operation pending on the existing one.
if (defined(childSurfaceTile.upsampledTerrain)) {
childSurfaceTile.upsampledTerrain.freeResources();
}
childSurfaceTile.upsampledTerrain = new TileTerrain({
data : surfaceTile.terrainData,
x : tile.x,
y : tile.y,
level : tile.level
});
if (surfaceTile.terrainData.isChildAvailable(tile.x, tile.y, childTile.x, childTile.y)) {
// Data is available for the child now. It might have been before, too.
if (!defined(childSurfaceTile.loadedTerrain)) {
// No load process is in progress, so start one.
childSurfaceTile.loadedTerrain = new TileTerrain();
}
}
childTile.state = QuadtreeTileLoadState.LOADING;
}
}
function isDataAvailable(tile, terrainProvider) {
var tileDataAvailable = terrainProvider.getTileDataAvailable(tile.x, tile.y, tile.level);
if (defined(tileDataAvailable)) {
return tileDataAvailable;
}
var parent = tile.parent;
if (!defined(parent)) {
// Data is assumed to be available for root tiles.
return true;
}
if (!defined(parent.data) || !defined(parent.data.terrainData)) {
// Parent tile data is not yet received or upsampled, so assume (for now) that this
// child tile is not available.
return false;
}
return parent.data.terrainData.isChildAvailable(parent.x, parent.y, tile.x, tile.y);
}
function getContextWaterMaskData(context) {
var data = context.cache.tile_waterMaskData;
if (!defined(data)) {
var allWaterTexture = new Texture({
context : context,
pixelFormat : PixelFormat.LUMINANCE,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
source : {
arrayBufferView : new Uint8Array([255]),
width : 1,
height : 1
}
});
allWaterTexture.referenceCount = 1;
var sampler = new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
minificationFilter : TextureMinificationFilter.LINEAR,
magnificationFilter : TextureMagnificationFilter.LINEAR
});
data = {
allWaterTexture : allWaterTexture,
sampler : sampler,
destroy : function() {
this.allWaterTexture.destroy();
}
};
context.cache.tile_waterMaskData = data;
}
return data;
}
function createWaterMaskTextureIfNeeded(context, surfaceTile) {
var previousTexture = surfaceTile.waterMaskTexture;
if (defined(previousTexture)) {
--previousTexture.referenceCount;
if (previousTexture.referenceCount === 0) {
previousTexture.destroy();
}
surfaceTile.waterMaskTexture = undefined;
}
var waterMask = surfaceTile.terrainData.waterMask;
if (!defined(waterMask)) {
return;
}
var waterMaskData = getContextWaterMaskData(context);
var texture;
var waterMaskLength = waterMask.length;
if (waterMaskLength === 1) {
// Length 1 means the tile is entirely land or entirely water.
// A value of 0 indicates entirely land, a value of 1 indicates entirely water.
if (waterMask[0] !== 0) {
texture = waterMaskData.allWaterTexture;
} else {
// Leave the texture undefined if the tile is entirely land.
return;
}
} else {
var textureSize = Math.sqrt(waterMaskLength);
texture = new Texture({
context : context,
pixelFormat : PixelFormat.LUMINANCE,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
source : {
width : textureSize,
height : textureSize,
arrayBufferView : waterMask
},
sampler : waterMaskData.sampler
});
texture.referenceCount = 0;
}
++texture.referenceCount;
surfaceTile.waterMaskTexture = texture;
Cartesian4.fromElements(0.0, 0.0, 1.0, 1.0, surfaceTile.waterMaskTranslationAndScale);
}
function upsampleWaterMask(tile) {
var surfaceTile = tile.data;
// Find the nearest ancestor with loaded terrain.
var sourceTile = tile.parent;
while (defined(sourceTile) && !defined(sourceTile.data.terrainData) || sourceTile.data.terrainData.wasCreatedByUpsampling()) {
sourceTile = sourceTile.parent;
}
if (!defined(sourceTile) || !defined(sourceTile.data.waterMaskTexture)) {
// No ancestors have a water mask texture - try again later.
return;
}
surfaceTile.waterMaskTexture = sourceTile.data.waterMaskTexture;
++surfaceTile.waterMaskTexture.referenceCount;
// Compute the water mask translation and scale
var sourceTileRectangle = sourceTile.rectangle;
var tileRectangle = tile.rectangle;
var tileWidth = tileRectangle.width;
var tileHeight = tileRectangle.height;
var scaleX = tileWidth / sourceTileRectangle.width;
var scaleY = tileHeight / sourceTileRectangle.height;
surfaceTile.waterMaskTranslationAndScale.x = scaleX * (tileRectangle.west - sourceTileRectangle.west) / tileWidth;
surfaceTile.waterMaskTranslationAndScale.y = scaleY * (tileRectangle.south - sourceTileRectangle.south) / tileHeight;
surfaceTile.waterMaskTranslationAndScale.z = scaleX;
surfaceTile.waterMaskTranslationAndScale.w = scaleY;
}
return GlobeSurfaceTile;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ReprojectWebMercatorFS',[],function() {
'use strict';
return "uniform sampler2D u_texture;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ReprojectWebMercatorVS',[],function() {
'use strict';
return "attribute vec4 position;\n\
attribute float webMercatorT;\n\
uniform vec2 u_textureDimensions;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
v_textureCoordinates = vec2(position.x, webMercatorT);\n\
gl_Position = czm_viewportOrthographic * (position * vec4(u_textureDimensions, 1.0, 1.0));\n\
}\n\
";
});
/*global define*/
define('Scene/Imagery',[
'../Core/defined',
'../Core/destroyObject',
'./ImageryState'
], function(
defined,
destroyObject,
ImageryState) {
'use strict';
/**
* Stores details about a tile of imagery.
*
* @alias Imagery
* @private
*/
function Imagery(imageryLayer, x, y, level, rectangle) {
this.imageryLayer = imageryLayer;
this.x = x;
this.y = y;
this.level = level;
if (level !== 0) {
var parentX = x / 2 | 0;
var parentY = y / 2 | 0;
var parentLevel = level - 1;
this.parent = imageryLayer.getImageryFromCache(parentX, parentY, parentLevel);
}
this.state = ImageryState.UNLOADED;
this.imageUrl = undefined;
this.image = undefined;
this.texture = undefined;
this.textureWebMercator = undefined;
this.credits = undefined;
this.referenceCount = 0;
if (!defined(rectangle) && imageryLayer.imageryProvider.ready) {
var tilingScheme = imageryLayer.imageryProvider.tilingScheme;
rectangle = tilingScheme.tileXYToRectangle(x, y, level);
}
this.rectangle = rectangle;
}
Imagery.createPlaceholder = function(imageryLayer) {
var result = new Imagery(imageryLayer, 0, 0, 0);
result.addReference();
result.state = ImageryState.PLACEHOLDER;
return result;
};
Imagery.prototype.addReference = function() {
++this.referenceCount;
};
Imagery.prototype.releaseReference = function() {
--this.referenceCount;
if (this.referenceCount === 0) {
this.imageryLayer.removeImageryFromCache(this);
if (defined(this.parent)) {
this.parent.releaseReference();
}
if (defined(this.image) && defined(this.image.destroy)) {
this.image.destroy();
}
if (defined(this.texture)) {
this.texture.destroy();
}
if (defined(this.textureWebMercator) && this.texture !== this.textureWebMercator) {
this.textureWebMercator.destroy();
}
destroyObject(this);
return 0;
}
return this.referenceCount;
};
Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection) {
if (this.state === ImageryState.UNLOADED) {
this.state = ImageryState.TRANSITIONING;
this.imageryLayer._requestImagery(this);
}
if (this.state === ImageryState.RECEIVED) {
this.state = ImageryState.TRANSITIONING;
this.imageryLayer._createTexture(frameState.context, this);
}
// If the imagery is already ready, but we need a geographic version and don't have it yet,
// we still need to do the reprojection step. This can happen if the Web Mercator version
// is fine initially, but the geographic one is needed later.
var needsReprojection = this.state === ImageryState.READY && needGeographicProjection && !this.texture;
if (this.state === ImageryState.TEXTURE_LOADED || needsReprojection) {
this.state = ImageryState.TRANSITIONING;
this.imageryLayer._reprojectTexture(frameState, this, needGeographicProjection);
}
};
return Imagery;
});
/*global define*/
define('Scene/TileImagery',[
'../Core/defined',
'./ImageryState'
], function(
defined,
ImageryState) {
'use strict';
/**
* The assocation between a terrain tile and an imagery tile.
*
* @alias TileImagery
* @private
*
* @param {Imagery} imagery The imagery tile.
* @param {Cartesian4} textureCoordinateRectangle The texture rectangle of the tile that is covered
* by the imagery, where X=west, Y=south, Z=east, W=north.
* @param {Boolean} useWebMercatorT true to use the Web Mercator texture coordinates for this imagery tile.
*/
function TileImagery(imagery, textureCoordinateRectangle, useWebMercatorT) {
this.readyImagery = undefined;
this.loadingImagery = imagery;
this.textureCoordinateRectangle = textureCoordinateRectangle;
this.textureTranslationAndScale = undefined;
this.useWebMercatorT = useWebMercatorT;
}
/**
* Frees the resources held by this instance.
*/
TileImagery.prototype.freeResources = function() {
if (defined(this.readyImagery)) {
this.readyImagery.releaseReference();
}
if (defined(this.loadingImagery)) {
this.loadingImagery.releaseReference();
}
};
/**
* Processes the load state machine for this instance.
*
* @param {Tile} tile The tile to which this instance belongs.
* @param {FrameState} frameState The frameState.
* @returns {Boolean} True if this instance is done loading; otherwise, false.
*/
TileImagery.prototype.processStateMachine = function(tile, frameState) {
var loadingImagery = this.loadingImagery;
var imageryLayer = loadingImagery.imageryLayer;
loadingImagery.processStateMachine(frameState, !this.useWebMercatorT);
if (loadingImagery.state === ImageryState.READY) {
if (defined(this.readyImagery)) {
this.readyImagery.releaseReference();
}
this.readyImagery = this.loadingImagery;
this.loadingImagery = undefined;
this.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, this);
return true; // done loading
}
// Find some ancestor imagery we can use while this imagery is still loading.
var ancestor = loadingImagery.parent;
var closestAncestorThatNeedsLoading;
while (defined(ancestor) && ancestor.state !== ImageryState.READY) {
if (ancestor.state !== ImageryState.FAILED && ancestor.state !== ImageryState.INVALID) {
// ancestor is still loading
closestAncestorThatNeedsLoading = closestAncestorThatNeedsLoading || ancestor;
}
ancestor = ancestor.parent;
}
if (this.readyImagery !== ancestor) {
if (defined(this.readyImagery)) {
this.readyImagery.releaseReference();
}
this.readyImagery = ancestor;
if (defined(ancestor)) {
ancestor.addReference();
this.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, this);
}
}
if (loadingImagery.state === ImageryState.FAILED || loadingImagery.state === ImageryState.INVALID) {
// The imagery tile is failed or invalid, so we'd like to use an ancestor instead.
if (defined(closestAncestorThatNeedsLoading)) {
// Push the ancestor's load process along a bit. This is necessary because some ancestor imagery
// tiles may not be attached directly to a terrain tile. Such tiles will never load if
// we don't do it here.
closestAncestorThatNeedsLoading.processStateMachine(frameState, !this.useWebMercatorT);
return false; // not done loading
} else {
// This imagery tile is failed or invalid, and we have the "best available" substitute.
return true; // done loading
}
}
return false; // not done loading
};
return TileImagery;
});
/*global define*/
define('Scene/ImageryLayer',[
'../Core/Cartesian2',
'../Core/Cartesian4',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/FeatureDetection',
'../Core/GeographicTilingScheme',
'../Core/IndexDatatype',
'../Core/Math',
'../Core/PixelFormat',
'../Core/Rectangle',
'../Core/TerrainProvider',
'../Core/TileProviderError',
'../Core/WebMercatorProjection',
'../Core/WebMercatorTilingScheme',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/ComputeCommand',
'../Renderer/ContextLimits',
'../Renderer/MipmapHint',
'../Renderer/Sampler',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/Texture',
'../Renderer/TextureMagnificationFilter',
'../Renderer/TextureMinificationFilter',
'../Renderer/TextureWrap',
'../Renderer/VertexArray',
'../Shaders/ReprojectWebMercatorFS',
'../Shaders/ReprojectWebMercatorVS',
'../ThirdParty/when',
'./Imagery',
'./ImageryState',
'./TileImagery'
], function(
Cartesian2,
Cartesian4,
defaultValue,
defined,
defineProperties,
destroyObject,
FeatureDetection,
GeographicTilingScheme,
IndexDatatype,
CesiumMath,
PixelFormat,
Rectangle,
TerrainProvider,
TileProviderError,
WebMercatorProjection,
WebMercatorTilingScheme,
Buffer,
BufferUsage,
ComputeCommand,
ContextLimits,
MipmapHint,
Sampler,
ShaderProgram,
ShaderSource,
Texture,
TextureMagnificationFilter,
TextureMinificationFilter,
TextureWrap,
VertexArray,
ReprojectWebMercatorFS,
ReprojectWebMercatorVS,
when,
Imagery,
ImageryState,
TileImagery) {
'use strict';
/**
* An imagery layer that displays tiled image data from a single imagery provider
* on a {@link Globe}.
*
* @alias ImageryLayer
* @constructor
*
* @param {ImageryProvider} imageryProvider The imagery provider to use.
* @param {Object} [options] Object with the following properties:
* @param {Rectangle} [options.rectangle=imageryProvider.rectangle] The rectangle of the layer. This rectangle
* can limit the visible portion of the imagery provider.
* @param {Number|Function} [options.alpha=1.0] The alpha blending value of this layer, from 0.0 to 1.0.
* This can either be a simple number or a function with the signature
* function(frameState, layer, x, y, level)
. The function is passed the
* current frame state, this layer, and the x, y, and level coordinates of the
* imagery tile for which the alpha is required, and it is expected to return
* the alpha value to use for the tile.
* @param {Number|Function} [options.brightness=1.0] The brightness of this layer. 1.0 uses the unmodified imagery
* color. Less than 1.0 makes the imagery darker while greater than 1.0 makes it brighter.
* This can either be a simple number or a function with the signature
* function(frameState, layer, x, y, level)
. The function is passed the
* current frame state, this layer, and the x, y, and level coordinates of the
* imagery tile for which the brightness is required, and it is expected to return
* the brightness value to use for the tile. The function is executed for every
* frame and for every tile, so it must be fast.
* @param {Number|Function} [options.contrast=1.0] The contrast of this layer. 1.0 uses the unmodified imagery color.
* Less than 1.0 reduces the contrast while greater than 1.0 increases it.
* This can either be a simple number or a function with the signature
* function(frameState, layer, x, y, level)
. The function is passed the
* current frame state, this layer, and the x, y, and level coordinates of the
* imagery tile for which the contrast is required, and it is expected to return
* the contrast value to use for the tile. The function is executed for every
* frame and for every tile, so it must be fast.
* @param {Number|Function} [options.hue=0.0] The hue of this layer. 0.0 uses the unmodified imagery color.
* This can either be a simple number or a function with the signature
* function(frameState, layer, x, y, level)
. The function is passed the
* current frame state, this layer, and the x, y, and level coordinates
* of the imagery tile for which the hue is required, and it is expected to return
* the contrast value to use for the tile. The function is executed for every
* frame and for every tile, so it must be fast.
* @param {Number|Function} [options.saturation=1.0] The saturation of this layer. 1.0 uses the unmodified imagery color.
* Less than 1.0 reduces the saturation while greater than 1.0 increases it.
* This can either be a simple number or a function with the signature
* function(frameState, layer, x, y, level)
. The function is passed the
* current frame state, this layer, and the x, y, and level coordinates
* of the imagery tile for which the saturation is required, and it is expected to return
* the contrast value to use for the tile. The function is executed for every
* frame and for every tile, so it must be fast.
* @param {Number|Function} [options.gamma=1.0] The gamma correction to apply to this layer. 1.0 uses the unmodified imagery color.
* This can either be a simple number or a function with the signature
* function(frameState, layer, x, y, level)
. The function is passed the
* current frame state, this layer, and the x, y, and level coordinates of the
* imagery tile for which the gamma is required, and it is expected to return
* the gamma value to use for the tile. The function is executed for every
* frame and for every tile, so it must be fast.
* @param {Boolean} [options.show=true] True if the layer is shown; otherwise, false.
* @param {Number} [options.maximumAnisotropy=maximum supported] The maximum anisotropy level to use
* for texture filtering. If this parameter is not specified, the maximum anisotropy supported
* by the WebGL stack will be used. Larger values make the imagery look better in horizon
* views.
* @param {Number} [options.minimumTerrainLevel] The minimum terrain level-of-detail at which to show this imagery layer,
* or undefined to show it at all levels. Level zero is the least-detailed level.
* @param {Number} [options.maximumTerrainLevel] The maximum terrain level-of-detail at which to show this imagery layer,
* or undefined to show it at all levels. Level zero is the least-detailed level.
*/
function ImageryLayer(imageryProvider, options) {
this._imageryProvider = imageryProvider;
options = defaultValue(options, {});
/**
* The alpha blending value of this layer, with 0.0 representing fully transparent and
* 1.0 representing fully opaque.
*
* @type {Number}
* @default 1.0
*/
this.alpha = defaultValue(options.alpha, defaultValue(imageryProvider.defaultAlpha, 1.0));
/**
* The brightness of this layer. 1.0 uses the unmodified imagery color. Less than 1.0
* makes the imagery darker while greater than 1.0 makes it brighter.
*
* @type {Number}
* @default {@link ImageryLayer.DEFAULT_BRIGHTNESS}
*/
this.brightness = defaultValue(options.brightness, defaultValue(imageryProvider.defaultBrightness, ImageryLayer.DEFAULT_BRIGHTNESS));
/**
* The contrast of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces
* the contrast while greater than 1.0 increases it.
*
* @type {Number}
* @default {@link ImageryLayer.DEFAULT_CONTRAST}
*/
this.contrast = defaultValue(options.contrast, defaultValue(imageryProvider.defaultContrast, ImageryLayer.DEFAULT_CONTRAST));
/**
* The hue of this layer in radians. 0.0 uses the unmodified imagery color.
*
* @type {Number}
* @default {@link ImageryLayer.DEFAULT_HUE}
*/
this.hue = defaultValue(options.hue, defaultValue(imageryProvider.defaultHue, ImageryLayer.DEFAULT_HUE));
/**
* The saturation of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces the
* saturation while greater than 1.0 increases it.
*
* @type {Number}
* @default {@link ImageryLayer.DEFAULT_SATURATION}
*/
this.saturation = defaultValue(options.saturation, defaultValue(imageryProvider.defaultSaturation, ImageryLayer.DEFAULT_SATURATION));
/**
* The gamma correction to apply to this layer. 1.0 uses the unmodified imagery color.
*
* @type {Number}
* @default {@link ImageryLayer.DEFAULT_GAMMA}
*/
this.gamma = defaultValue(options.gamma, defaultValue(imageryProvider.defaultGamma, ImageryLayer.DEFAULT_GAMMA));
/**
* Determines if this layer is shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
this._minimumTerrainLevel = options.minimumTerrainLevel;
this._maximumTerrainLevel = options.maximumTerrainLevel;
this._rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
this._maximumAnisotropy = options.maximumAnisotropy;
this._imageryCache = {};
this._skeletonPlaceholder = new TileImagery(Imagery.createPlaceholder(this));
// The value of the show property on the last update.
this._show = true;
// The index of this layer in the ImageryLayerCollection.
this._layerIndex = -1;
// true if this is the base (lowest shown) layer.
this._isBaseLayer = false;
this._requestImageError = undefined;
this._reprojectComputeCommands = [];
}
defineProperties(ImageryLayer.prototype, {
/**
* Gets the imagery provider for this layer.
* @memberof ImageryLayer.prototype
* @type {ImageryProvider}
* @readonly
*/
imageryProvider : {
get: function() {
return this._imageryProvider;
}
},
/**
* Gets the rectangle of this layer. If this rectangle is smaller than the rectangle of the
* {@link ImageryProvider}, only a portion of the imagery provider is shown.
* @memberof ImageryLayer.prototype
* @type {Rectangle}
* @readonly
*/
rectangle: {
get: function() {
return this._rectangle;
}
}
});
/**
* This value is used as the default brightness for the imagery layer if one is not provided during construction
* or by the imagery provider. This value does not modify the brightness of the imagery.
* @type {Number}
* @default 1.0
*/
ImageryLayer.DEFAULT_BRIGHTNESS = 1.0;
/**
* This value is used as the default contrast for the imagery layer if one is not provided during construction
* or by the imagery provider. This value does not modify the contrast of the imagery.
* @type {Number}
* @default 1.0
*/
ImageryLayer.DEFAULT_CONTRAST = 1.0;
/**
* This value is used as the default hue for the imagery layer if one is not provided during construction
* or by the imagery provider. This value does not modify the hue of the imagery.
* @type {Number}
* @default 0.0
*/
ImageryLayer.DEFAULT_HUE = 0.0;
/**
* This value is used as the default saturation for the imagery layer if one is not provided during construction
* or by the imagery provider. This value does not modify the saturation of the imagery.
* @type {Number}
* @default 1.0
*/
ImageryLayer.DEFAULT_SATURATION = 1.0;
/**
* This value is used as the default gamma for the imagery layer if one is not provided during construction
* or by the imagery provider. This value does not modify the gamma of the imagery.
* @type {Number}
* @default 1.0
*/
ImageryLayer.DEFAULT_GAMMA = 1.0;
/**
* Gets a value indicating whether this layer is the base layer in the
* {@link ImageryLayerCollection}. The base layer is the one that underlies all
* others. It is special in that it is treated as if it has global rectangle, even if
* it actually does not, by stretching the texels at the edges over the entire
* globe.
*
* @returns {Boolean} true if this is the base layer; otherwise, false.
*/
ImageryLayer.prototype.isBaseLayer = function() {
return this._isBaseLayer;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see ImageryLayer#destroy
*/
ImageryLayer.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* imageryLayer = imageryLayer && imageryLayer.destroy();
*
* @see ImageryLayer#isDestroyed
*/
ImageryLayer.prototype.destroy = function() {
return destroyObject(this);
};
var imageryBoundsScratch = new Rectangle();
var tileImageryBoundsScratch = new Rectangle();
var clippedRectangleScratch = new Rectangle();
var terrainRectangleScratch = new Rectangle();
/**
* Computes the intersection of this layer's rectangle with the imagery provider's availability rectangle,
* producing the overall bounds of imagery that can be produced by this layer.
*
* @returns {Promise.} A promise to a rectangle which defines the overall bounds of imagery that can be produced by this layer.
*
* @example
* // Zoom to an imagery layer.
* imageryLayer.getViewableRectangle().then(function (rectangle) {
* return camera.flyTo({
* destination: rectangle
* });
* });
*/
ImageryLayer.prototype.getViewableRectangle = function() {
var imageryProvider = this._imageryProvider;
var rectangle = this._rectangle;
return imageryProvider.readyPromise.then(function() {
return Rectangle.intersection(imageryProvider.rectangle, rectangle);
});
};
/**
* Create skeletons for the imagery tiles that partially or completely overlap a given terrain
* tile.
*
* @private
*
* @param {Tile} tile The terrain tile.
* @param {TerrainProvider} terrainProvider The terrain provider associated with the terrain tile.
* @param {Number} insertionPoint The position to insert new skeletons before in the tile's imagery list.
* @returns {Boolean} true if this layer overlaps any portion of the terrain tile; otherwise, false.
*/
ImageryLayer.prototype._createTileImagerySkeletons = function(tile, terrainProvider, insertionPoint) {
var surfaceTile = tile.data;
if (defined(this._minimumTerrainLevel) && tile.level < this._minimumTerrainLevel) {
return false;
}
if (defined(this._maximumTerrainLevel) && tile.level > this._maximumTerrainLevel) {
return false;
}
var imageryProvider = this._imageryProvider;
if (!defined(insertionPoint)) {
insertionPoint = surfaceTile.imagery.length;
}
if (!imageryProvider.ready) {
// The imagery provider is not ready, so we can't create skeletons, yet.
// Instead, add a placeholder so that we'll know to create
// the skeletons once the provider is ready.
this._skeletonPlaceholder.loadingImagery.addReference();
surfaceTile.imagery.splice(insertionPoint, 0, this._skeletonPlaceholder);
return true;
}
// Use Web Mercator for our texture coordinate computations if this imagery layer uses
// that projection and the terrain tile falls entirely inside the valid bounds of the
// projection.
var useWebMercatorT = imageryProvider.tilingScheme instanceof WebMercatorTilingScheme &&
tile.rectangle.north < WebMercatorProjection.MaximumLatitude &&
tile.rectangle.south > -WebMercatorProjection.MaximumLatitude;
// Compute the rectangle of the imagery from this imageryProvider that overlaps
// the geometry tile. The ImageryProvider and ImageryLayer both have the
// opportunity to constrain the rectangle. The imagery TilingScheme's rectangle
// always fully contains the ImageryProvider's rectangle.
var imageryBounds = Rectangle.intersection(imageryProvider.rectangle, this._rectangle, imageryBoundsScratch);
var rectangle = Rectangle.intersection(tile.rectangle, imageryBounds, tileImageryBoundsScratch);
if (!defined(rectangle)) {
// There is no overlap between this terrain tile and this imagery
// provider. Unless this is the base layer, no skeletons need to be created.
// We stretch texels at the edge of the base layer over the entire globe.
if (!this.isBaseLayer()) {
return false;
}
var baseImageryRectangle = imageryBounds;
var baseTerrainRectangle = tile.rectangle;
rectangle = tileImageryBoundsScratch;
if (baseTerrainRectangle.south >= baseImageryRectangle.north) {
rectangle.north = rectangle.south = baseImageryRectangle.north;
} else if (baseTerrainRectangle.north <= baseImageryRectangle.south) {
rectangle.north = rectangle.south = baseImageryRectangle.south;
} else {
rectangle.south = Math.max(baseTerrainRectangle.south, baseImageryRectangle.south);
rectangle.north = Math.min(baseTerrainRectangle.north, baseImageryRectangle.north);
}
if (baseTerrainRectangle.west >= baseImageryRectangle.east) {
rectangle.west = rectangle.east = baseImageryRectangle.east;
} else if (baseTerrainRectangle.east <= baseImageryRectangle.west) {
rectangle.west = rectangle.east = baseImageryRectangle.west;
} else {
rectangle.west = Math.max(baseTerrainRectangle.west, baseImageryRectangle.west);
rectangle.east = Math.min(baseTerrainRectangle.east, baseImageryRectangle.east);
}
}
var latitudeClosestToEquator = 0.0;
if (rectangle.south > 0.0) {
latitudeClosestToEquator = rectangle.south;
} else if (rectangle.north < 0.0) {
latitudeClosestToEquator = rectangle.north;
}
// Compute the required level in the imagery tiling scheme.
// The errorRatio should really be imagerySSE / terrainSSE rather than this hard-coded value.
// But first we need configurable imagery SSE and we need the rendering to be able to handle more
// images attached to a terrain tile than there are available texture units. So that's for the future.
var errorRatio = 1.0;
var targetGeometricError = errorRatio * terrainProvider.getLevelMaximumGeometricError(tile.level);
var imageryLevel = getLevelWithMaximumTexelSpacing(this, targetGeometricError, latitudeClosestToEquator);
imageryLevel = Math.max(0, imageryLevel);
var maximumLevel = imageryProvider.maximumLevel;
if (imageryLevel > maximumLevel) {
imageryLevel = maximumLevel;
}
if (defined(imageryProvider.minimumLevel)) {
var minimumLevel = imageryProvider.minimumLevel;
if (imageryLevel < minimumLevel) {
imageryLevel = minimumLevel;
}
}
var imageryTilingScheme = imageryProvider.tilingScheme;
var northwestTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.northwest(rectangle), imageryLevel);
var southeastTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.southeast(rectangle), imageryLevel);
// If the southeast corner of the rectangle lies very close to the north or west side
// of the southeast tile, we don't actually need the southernmost or easternmost
// tiles.
// Similarly, if the northwest corner of the rectangle lies very close to the south or east side
// of the northwest tile, we don't actually need the northernmost or westernmost tiles.
// We define "very close" as being within 1/512 of the width of the tile.
var veryCloseX = tile.rectangle.width / 512.0;
var veryCloseY = tile.rectangle.height / 512.0;
var northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
if (Math.abs(northwestTileRectangle.south - tile.rectangle.north) < veryCloseY && northwestTileCoordinates.y < southeastTileCoordinates.y) {
++northwestTileCoordinates.y;
}
if (Math.abs(northwestTileRectangle.east - tile.rectangle.west) < veryCloseX && northwestTileCoordinates.x < southeastTileCoordinates.x) {
++northwestTileCoordinates.x;
}
var southeastTileRectangle = imageryTilingScheme.tileXYToRectangle(southeastTileCoordinates.x, southeastTileCoordinates.y, imageryLevel);
if (Math.abs(southeastTileRectangle.north - tile.rectangle.south) < veryCloseY && southeastTileCoordinates.y > northwestTileCoordinates.y) {
--southeastTileCoordinates.y;
}
if (Math.abs(southeastTileRectangle.west - tile.rectangle.east) < veryCloseX && southeastTileCoordinates.x > northwestTileCoordinates.x) {
--southeastTileCoordinates.x;
}
// Create TileImagery instances for each imagery tile overlapping this terrain tile.
// We need to do all texture coordinate computations in the imagery tile's tiling scheme.
var terrainRectangle = Rectangle.clone(tile.rectangle, terrainRectangleScratch);
var imageryRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
var imageryTileXYToRectangle;
if (useWebMercatorT) {
imageryTilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangle);
imageryTilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryRectangle);
imageryTilingScheme.rectangleToNativeRectangle(clippedImageryRectangle, clippedImageryRectangle);
imageryTilingScheme.rectangleToNativeRectangle(imageryBounds, imageryBounds);
imageryTileXYToRectangle = imageryTilingScheme.tileXYToNativeRectangle.bind(imageryTilingScheme);
veryCloseX = terrainRectangle.width / 512.0;
veryCloseY = terrainRectangle.height / 512.0;
} else {
imageryTileXYToRectangle = imageryTilingScheme.tileXYToRectangle.bind(imageryTilingScheme);
}
var minU;
var maxU = 0.0;
var minV = 1.0;
var maxV;
// If this is the northern-most or western-most tile in the imagery tiling scheme,
// it may not start at the northern or western edge of the terrain tile.
// Calculate where it does start.
if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - terrainRectangle.west) >= veryCloseX) {
maxU = Math.min(1.0, (clippedImageryRectangle.west - terrainRectangle.west) / terrainRectangle.width);
}
if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - terrainRectangle.north) >= veryCloseY) {
minV = Math.max(0.0, (clippedImageryRectangle.north - terrainRectangle.south) / terrainRectangle.height);
}
var initialMinV = minV;
for ( var i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) {
minU = maxU;
imageryRectangle = imageryTileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel);
clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
if (!defined(clippedImageryRectangle)) {
continue;
}
maxU = Math.min(1.0, (clippedImageryRectangle.east - terrainRectangle.west) / terrainRectangle.width);
// If this is the eastern-most imagery tile mapped to this terrain tile,
// and there are more imagery tiles to the east of this one, the maxU
// should be 1.0 to make sure rounding errors don't make the last
// image fall shy of the edge of the terrain tile.
if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - terrainRectangle.east) < veryCloseX)) {
maxU = 1.0;
}
minV = initialMinV;
for ( var j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) {
maxV = minV;
imageryRectangle = imageryTileXYToRectangle(i, j, imageryLevel);
clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch);
if (!defined(clippedImageryRectangle)) {
continue;
}
minV = Math.max(0.0, (clippedImageryRectangle.south - terrainRectangle.south) / terrainRectangle.height);
// If this is the southern-most imagery tile mapped to this terrain tile,
// and there are more imagery tiles to the south of this one, the minV
// should be 0.0 to make sure rounding errors don't make the last
// image fall shy of the edge of the terrain tile.
if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - terrainRectangle.south) < veryCloseY)) {
minV = 0.0;
}
var texCoordsRectangle = new Cartesian4(minU, minV, maxU, maxV);
var imagery = this.getImageryFromCache(i, j, imageryLevel);
surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle, useWebMercatorT));
++insertionPoint;
}
}
return true;
};
/**
* Calculate the translation and scale for a particular {@link TileImagery} attached to a
* particular terrain tile.
*
* @private
*
* @param {Tile} tile The terrain tile.
* @param {TileImagery} tileImagery The imagery tile mapping.
* @returns {Cartesian4} The translation and scale where X and Y are the translation and Z and W
* are the scale.
*/
ImageryLayer.prototype._calculateTextureTranslationAndScale = function(tile, tileImagery) {
var imageryRectangle = tileImagery.readyImagery.rectangle;
var terrainRectangle = tile.rectangle;
if (tileImagery.useWebMercatorT) {
var tilingScheme = tileImagery.readyImagery.imageryLayer.imageryProvider.tilingScheme;
imageryRectangle = tilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryBoundsScratch);
terrainRectangle = tilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangleScratch);
}
var terrainWidth = terrainRectangle.width;
var terrainHeight = terrainRectangle.height;
var scaleX = terrainWidth / imageryRectangle.width;
var scaleY = terrainHeight / imageryRectangle.height;
return new Cartesian4(
scaleX * (terrainRectangle.west - imageryRectangle.west) / terrainWidth,
scaleY * (terrainRectangle.south - imageryRectangle.south) / terrainHeight,
scaleX,
scaleY);
};
/**
* Request a particular piece of imagery from the imagery provider. This method handles raising an
* error event if the request fails, and retrying the request if necessary.
*
* @private
*
* @param {Imagery} imagery The imagery to request.
*/
ImageryLayer.prototype._requestImagery = function(imagery) {
var imageryProvider = this._imageryProvider;
var that = this;
function success(image) {
if (!defined(image)) {
return failure();
}
imagery.image = image;
imagery.state = ImageryState.RECEIVED;
TileProviderError.handleSuccess(that._requestImageError);
}
function failure(e) {
// Initially assume failure. handleError may retry, in which case the state will
// change to TRANSITIONING.
imagery.state = ImageryState.FAILED;
var message = 'Failed to obtain image tile X: ' + imagery.x + ' Y: ' + imagery.y + ' Level: ' + imagery.level + '.';
that._requestImageError = TileProviderError.handleError(
that._requestImageError,
imageryProvider,
imageryProvider.errorEvent,
message,
imagery.x, imagery.y, imagery.level,
doRequest,
e);
}
function doRequest() {
imagery.state = ImageryState.TRANSITIONING;
var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level);
if (!defined(imagePromise)) {
// Too many parallel requests, so postpone loading tile.
imagery.state = ImageryState.UNLOADED;
return;
}
if (defined(imageryProvider.getTileCredits)) {
imagery.credits = imageryProvider.getTileCredits(imagery.x, imagery.y, imagery.level);
}
when(imagePromise, success, failure);
}
doRequest();
};
/**
* Create a WebGL texture for a given {@link Imagery} instance.
*
* @private
*
* @param {Context} context The rendered context to use to create textures.
* @param {Imagery} imagery The imagery for which to create a texture.
*/
ImageryLayer.prototype._createTexture = function(context, imagery) {
var imageryProvider = this._imageryProvider;
// If this imagery provider has a discard policy, use it to check if this
// image should be discarded.
if (defined(imageryProvider.tileDiscardPolicy)) {
var discardPolicy = imageryProvider.tileDiscardPolicy;
if (defined(discardPolicy)) {
// If the discard policy is not ready yet, transition back to the
// RECEIVED state and we'll try again next time.
if (!discardPolicy.isReady()) {
imagery.state = ImageryState.RECEIVED;
return;
}
// Mark discarded imagery tiles invalid. Parent imagery will be used instead.
if (discardPolicy.shouldDiscardImage(imagery.image)) {
imagery.state = ImageryState.INVALID;
return;
}
}
}
// Imagery does not need to be discarded, so upload it to WebGL.
var texture = new Texture({
context : context,
source : imagery.image,
pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB
});
if (imageryProvider.tilingScheme instanceof WebMercatorTilingScheme) {
imagery.textureWebMercator = texture;
} else {
imagery.texture = texture;
}
imagery.image = undefined;
imagery.state = ImageryState.TEXTURE_LOADED;
};
function finalizeReprojectTexture(imageryLayer, context, imagery, texture) {
// Use mipmaps if this texture has power-of-two dimensions.
if (CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) {
var mipmapSampler = context.cache.imageryLayer_mipmapSampler;
if (!defined(mipmapSampler)) {
var maximumSupportedAnisotropy = ContextLimits.maximumTextureFilterAnisotropy;
mipmapSampler = context.cache.imageryLayer_mipmapSampler = new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
minificationFilter : TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
magnificationFilter : TextureMagnificationFilter.LINEAR,
maximumAnisotropy : Math.min(maximumSupportedAnisotropy, defaultValue(imageryLayer._maximumAnisotropy, maximumSupportedAnisotropy))
});
}
texture.generateMipmap(MipmapHint.NICEST);
texture.sampler = mipmapSampler;
} else {
var nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler;
if (!defined(nonMipmapSampler)) {
nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler = new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
minificationFilter : TextureMinificationFilter.LINEAR,
magnificationFilter : TextureMagnificationFilter.LINEAR
});
}
texture.sampler = nonMipmapSampler;
}
imagery.state = ImageryState.READY;
}
/**
* Enqueues a command re-projecting a texture to a {@link GeographicProjection} on the next update, if necessary, and generate
* mipmaps for the geographic texture.
*
* @private
*
* @param {FrameState} frameState The frameState.
* @param {Imagery} imagery The imagery instance to reproject.
* @param {Boolean} [needGeographicProjection=true] True to reproject to geographic, or false if Web Mercator is fine.
*/
ImageryLayer.prototype._reprojectTexture = function(frameState, imagery, needGeographicProjection) {
var texture = imagery.textureWebMercator || imagery.texture;
var rectangle = imagery.rectangle;
var context = frameState.context;
needGeographicProjection = defaultValue(needGeographicProjection, true);
// Reproject this texture if it is not already in a geographic projection and
// the pixels are more than 1e-5 radians apart. The pixel spacing cutoff
// avoids precision problems in the reprojection transformation while making
// no noticeable difference in the georeferencing of the image.
if (needGeographicProjection &&
!(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) &&
rectangle.width / texture.width > 1e-5) {
var that = this;
imagery.addReference();
var computeCommand = new ComputeCommand({
persists : true,
owner : this,
// Update render resources right before execution instead of now.
// This allows different ImageryLayers to share the same vao and buffers.
preExecute : function(command) {
reprojectToGeographic(command, context, texture, imagery.rectangle);
},
postExecute : function(outputTexture) {
imagery.texture = outputTexture;
finalizeReprojectTexture(that, context, imagery, outputTexture);
imagery.releaseReference();
}
});
this._reprojectComputeCommands.push(computeCommand);
} else {
if (needGeographicProjection) {
imagery.texture = texture;
}
finalizeReprojectTexture(this, context, imagery, texture);
}
};
/**
* Updates frame state to execute any queued texture re-projections.
*
* @private
*
* @param {FrameState} frameState The frameState.
*/
ImageryLayer.prototype.queueReprojectionCommands = function(frameState) {
var computeCommands = this._reprojectComputeCommands;
var length = computeCommands.length;
for (var i = 0; i < length; ++i) {
frameState.commandList.push(computeCommands[i]);
}
computeCommands.length = 0;
};
/**
* Cancels re-projection commands queued for the next frame.
*
* @private
*/
ImageryLayer.prototype.cancelReprojections = function() {
this._reprojectComputeCommands.length = 0;
};
ImageryLayer.prototype.getImageryFromCache = function(x, y, level, imageryRectangle) {
var cacheKey = getImageryCacheKey(x, y, level);
var imagery = this._imageryCache[cacheKey];
if (!defined(imagery)) {
imagery = new Imagery(this, x, y, level, imageryRectangle);
this._imageryCache[cacheKey] = imagery;
}
imagery.addReference();
return imagery;
};
ImageryLayer.prototype.removeImageryFromCache = function(imagery) {
var cacheKey = getImageryCacheKey(imagery.x, imagery.y, imagery.level);
delete this._imageryCache[cacheKey];
};
function getImageryCacheKey(x, y, level) {
return JSON.stringify([x, y, level]);
}
var uniformMap = {
u_textureDimensions : function() {
return this.textureDimensions;
},
u_texture : function() {
return this.texture;
},
textureDimensions : new Cartesian2(),
texture : undefined
};
var float32ArrayScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(2 * 64) : undefined;
function reprojectToGeographic(command, context, texture, rectangle) {
// This function has gone through a number of iterations, because GPUs are awesome.
//
// Originally, we had a very simple vertex shader and computed the Web Mercator texture coordinates
// per-fragment in the fragment shader. That worked well, except on mobile devices, because
// fragment shaders have limited precision on many mobile devices. The result was smearing artifacts
// at medium zoom levels because different geographic texture coordinates would be reprojected to Web
// Mercator as the same value.
//
// Our solution was to reproject to Web Mercator in the vertex shader instead of the fragment shader.
// This required far more vertex data. With fragment shader reprojection, we only needed a single quad.
// But to achieve the same precision with vertex shader reprojection, we needed a vertex for each
// output pixel. So we used a grid of 256x256 vertices, because most of our imagery
// tiles are 256x256. Fortunately the grid could be created and uploaded to the GPU just once and
// re-used for all reprojections, so the performance was virtually unchanged from our original fragment
// shader approach. See https://github.com/AnalyticalGraphicsInc/cesium/pull/714.
//
// Over a year later, we noticed (https://github.com/AnalyticalGraphicsInc/cesium/issues/2110)
// that our reprojection code was creating a rare but severe artifact on some GPUs (Intel HD 4600
// for one). The problem was that the GLSL sin function on these GPUs had a discontinuity at fine scales in
// a few places.
//
// We solved this by implementing a more reliable sin function based on the CORDIC algorithm
// (https://github.com/AnalyticalGraphicsInc/cesium/pull/2111). Even though this was a fair
// amount of code to be executing per vertex, the performance seemed to be pretty good on most GPUs.
// Unfortunately, on some GPUs, the performance was absolutely terrible
// (https://github.com/AnalyticalGraphicsInc/cesium/issues/2258).
//
// So that brings us to our current solution, the one you see here. Effectively, we compute the Web
// Mercator texture coordinates on the CPU and store the T coordinate with each vertex (the S coordinate
// is the same in Geographic and Web Mercator). To make this faster, we reduced our reprojection mesh
// to be only 2 vertices wide and 64 vertices high. We should have reduced the width to 2 sooner,
// because the extra vertices weren't buying us anything. The height of 64 means we are technically
// doing a slightly less accurate reprojection than we were before, but we can't see the difference
// so it's worth the 4x speedup.
var reproject = context.cache.imageryLayer_reproject;
if (!defined(reproject)) {
reproject = context.cache.imageryLayer_reproject = {
vertexArray : undefined,
shaderProgram : undefined,
sampler : undefined,
destroy : function() {
if (defined(this.framebuffer)) {
this.framebuffer.destroy();
}
if (defined(this.vertexArray)) {
this.vertexArray.destroy();
}
if (defined(this.shaderProgram)) {
this.shaderProgram.destroy();
}
}
};
var positions = new Float32Array(2 * 64 * 2);
var index = 0;
for (var j = 0; j < 64; ++j) {
var y = j / 63.0;
positions[index++] = 0.0;
positions[index++] = y;
positions[index++] = 1.0;
positions[index++] = y;
}
var reprojectAttributeIndices = {
position : 0,
webMercatorT : 1
};
var indices = TerrainProvider.getRegularGridIndices(2, 64);
var indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : indices,
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
reproject.vertexArray = new VertexArray({
context : context,
attributes : [{
index : reprojectAttributeIndices.position,
vertexBuffer : Buffer.createVertexBuffer({
context : context,
typedArray : positions,
usage : BufferUsage.STATIC_DRAW
}),
componentsPerAttribute : 2
},{
index : reprojectAttributeIndices.webMercatorT,
vertexBuffer : Buffer.createVertexBuffer({
context : context,
sizeInBytes : 64 * 2 * 4,
usage : BufferUsage.STREAM_DRAW
}),
componentsPerAttribute : 1
}],
indexBuffer : indexBuffer
});
var vs = new ShaderSource({
sources : [ReprojectWebMercatorVS]
});
reproject.shaderProgram = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : ReprojectWebMercatorFS,
attributeLocations : reprojectAttributeIndices
});
reproject.sampler = new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
minificationFilter : TextureMinificationFilter.LINEAR,
magnificationFilter : TextureMagnificationFilter.LINEAR
});
}
texture.sampler = reproject.sampler;
var width = texture.width;
var height = texture.height;
uniformMap.textureDimensions.x = width;
uniformMap.textureDimensions.y = height;
uniformMap.texture = texture;
var sinLatitude = Math.sin(rectangle.south);
var southMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
sinLatitude = Math.sin(rectangle.north);
var northMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
var oneOverMercatorHeight = 1.0 / (northMercatorY - southMercatorY);
var outputTexture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : texture.pixelFormat,
pixelDatatype : texture.pixelDatatype,
preMultiplyAlpha : texture.preMultiplyAlpha
});
// Allocate memory for the mipmaps. Failure to do this before rendering
// to the texture via the FBO, and calling generateMipmap later,
// will result in the texture appearing blank. I can't pretend to
// understand exactly why this is.
if (CesiumMath.isPowerOfTwo(width) && CesiumMath.isPowerOfTwo(height)) {
outputTexture.generateMipmap(MipmapHint.NICEST);
}
var south = rectangle.south;
var north = rectangle.north;
var webMercatorT = float32ArrayScratch;
var outputIndex = 0;
for (var webMercatorTIndex = 0; webMercatorTIndex < 64; ++webMercatorTIndex) {
var fraction = webMercatorTIndex / 63.0;
var latitude = CesiumMath.lerp(south, north, fraction);
sinLatitude = Math.sin(latitude);
var mercatorY = 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
var mercatorFraction = (mercatorY - southMercatorY) * oneOverMercatorHeight;
webMercatorT[outputIndex++] = mercatorFraction;
webMercatorT[outputIndex++] = mercatorFraction;
}
reproject.vertexArray.getAttribute(1).vertexBuffer.copyFromArrayView(webMercatorT);
command.shaderProgram = reproject.shaderProgram;
command.outputTexture = outputTexture;
command.uniformMap = uniformMap;
command.vertexArray = reproject.vertexArray;
}
/**
* Gets the level with the specified world coordinate spacing between texels, or less.
*
* @param {Number} texelSpacing The texel spacing for which to find a corresponding level.
* @param {Number} latitudeClosestToEquator The latitude closest to the equator that we're concerned with.
* @returns {Number} The level with the specified texel spacing or less.
*/
function getLevelWithMaximumTexelSpacing(layer, texelSpacing, latitudeClosestToEquator) {
// PERFORMANCE_IDEA: factor out the stuff that doesn't change.
var imageryProvider = layer._imageryProvider;
var tilingScheme = imageryProvider.tilingScheme;
var ellipsoid = tilingScheme.ellipsoid;
var latitudeFactor = !(layer._imageryProvider.tilingScheme instanceof GeographicTilingScheme) ? Math.cos(latitudeClosestToEquator) : 1.0;
var tilingSchemeRectangle = tilingScheme.rectangle;
var levelZeroMaximumTexelSpacing = ellipsoid.maximumRadius * tilingSchemeRectangle.width * latitudeFactor / (imageryProvider.tileWidth * tilingScheme.getNumberOfXTilesAtLevel(0));
var twoToTheLevelPower = levelZeroMaximumTexelSpacing / texelSpacing;
var level = Math.log(twoToTheLevelPower) / Math.log(2);
var rounded = Math.round(level);
return rounded | 0;
}
return ImageryLayer;
});
/*global define*/
define('Scene/GlobeSurfaceTileProvider',[
'../Core/BoundingSphere',
'../Core/BoxOutlineGeometry',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Event',
'../Core/GeometryInstance',
'../Core/GeometryPipeline',
'../Core/IndexDatatype',
'../Core/Intersect',
'../Core/Math',
'../Core/Matrix4',
'../Core/OrientedBoundingBox',
'../Core/PrimitiveType',
'../Core/Rectangle',
'../Core/SphereOutlineGeometry',
'../Core/TerrainQuantization',
'../Core/Visibility',
'../Core/WebMercatorProjection',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/ContextLimits',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/VertexArray',
'../Scene/BlendingState',
'../Scene/DepthFunction',
'../Scene/PerInstanceColorAppearance',
'../Scene/Primitive',
'./GlobeSurfaceTile',
'./ImageryLayer',
'./QuadtreeTileLoadState',
'./SceneMode',
'./ShadowMode'
], function(
BoundingSphere,
BoxOutlineGeometry,
Cartesian2,
Cartesian3,
Cartesian4,
Color,
ColorGeometryInstanceAttribute,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Event,
GeometryInstance,
GeometryPipeline,
IndexDatatype,
Intersect,
CesiumMath,
Matrix4,
OrientedBoundingBox,
PrimitiveType,
Rectangle,
SphereOutlineGeometry,
TerrainQuantization,
Visibility,
WebMercatorProjection,
Buffer,
BufferUsage,
ContextLimits,
DrawCommand,
Pass,
RenderState,
VertexArray,
BlendingState,
DepthFunction,
PerInstanceColorAppearance,
Primitive,
GlobeSurfaceTile,
ImageryLayer,
QuadtreeTileLoadState,
SceneMode,
ShadowMode) {
'use strict';
/**
* Provides quadtree tiles representing the surface of the globe. This type is intended to be used
* with {@link QuadtreePrimitive}.
*
* @alias GlobeSurfaceTileProvider
* @constructor
*
* @param {TerrainProvider} options.terrainProvider The terrain provider that describes the surface geometry.
* @param {ImageryLayerCollection} option.imageryLayers The collection of imagery layers describing the shading of the surface.
* @param {GlobeSurfaceShaderSet} options.surfaceShaderSet The set of shaders used to render the surface.
*
* @private
*/
function GlobeSurfaceTileProvider(options) {
if (!defined(options)) {
throw new DeveloperError('options is required.');
}
if (!defined(options.terrainProvider)) {
throw new DeveloperError('options.terrainProvider is required.');
} else if (!defined(options.imageryLayers)) {
throw new DeveloperError('options.imageryLayers is required.');
} else if (!defined(options.surfaceShaderSet)) {
throw new DeveloperError('options.surfaceShaderSet is required.');
}
this.lightingFadeOutDistance = 6500000.0;
this.lightingFadeInDistance = 9000000.0;
this.hasWaterMask = false;
this.oceanNormalMap = undefined;
this.zoomedOutOceanSpecularIntensity = 0.5;
this.enableLighting = false;
this.shadows = ShadowMode.RECEIVE_ONLY;
this._quadtree = undefined;
this._terrainProvider = options.terrainProvider;
this._imageryLayers = options.imageryLayers;
this._surfaceShaderSet = options.surfaceShaderSet;
this._renderState = undefined;
this._blendRenderState = undefined;
this._pickRenderState = undefined;
this._errorEvent = new Event();
this._imageryLayers.layerAdded.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerAdded, this);
this._imageryLayers.layerRemoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerRemoved, this);
this._imageryLayers.layerMoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerMoved, this);
this._imageryLayers.layerShownOrHidden.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden, this);
this._layerOrderChanged = false;
this._tilesToRenderByTextureCount = [];
this._drawCommands = [];
this._uniformMaps = [];
this._pickCommands = [];
this._usedDrawCommands = 0;
this._usedPickCommands = 0;
this._vertexArraysToDestroy = [];
this._debug = {
wireframe : false,
boundingSphereTile : undefined
};
this._baseColor = undefined;
this._firstPassInitialColor = undefined;
this.baseColor = new Color(0.0, 0.0, 0.5, 1.0);
}
defineProperties(GlobeSurfaceTileProvider.prototype, {
/**
* Gets or sets the color of the globe when no imagery is available.
* @memberof GlobeSurfaceTileProvider.prototype
* @type {Color}
*/
baseColor : {
get : function() {
return this._baseColor;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
this._baseColor = value;
this._firstPassInitialColor = Cartesian4.fromColor(value, this._firstPassInitialColor);
}
},
/**
* Gets or sets the {@link QuadtreePrimitive} for which this provider is
* providing tiles. This property may be undefined if the provider is not yet associated
* with a {@link QuadtreePrimitive}.
* @memberof GlobeSurfaceTileProvider.prototype
* @type {QuadtreePrimitive}
*/
quadtree : {
get : function() {
return this._quadtree;
},
set : function(value) {
if (!defined(value)) {
throw new DeveloperError('value is required.');
}
this._quadtree = value;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof GlobeSurfaceTileProvider.prototype
* @type {Boolean}
*/
ready : {
get : function() {
return this._terrainProvider.ready && (this._imageryLayers.length === 0 || this._imageryLayers.get(0).imageryProvider.ready);
}
},
/**
* Gets the tiling scheme used by the provider. This property should
* not be accessed before {@link GlobeSurfaceTileProvider#ready} returns true.
* @memberof GlobeSurfaceTileProvider.prototype
* @type {TilingScheme}
*/
tilingScheme : {
get : function() {
return this._terrainProvider.tilingScheme;
}
},
/**
* Gets an event that is raised when the geometry provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof GlobeSurfaceTileProvider.prototype
* @type {Event}
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets or sets the terrain provider that describes the surface geometry.
* @memberof GlobeSurfaceTileProvider.prototype
* @type {TerrainProvider}
*/
terrainProvider : {
get : function() {
return this._terrainProvider;
},
set : function(terrainProvider) {
if (this._terrainProvider === terrainProvider) {
return;
}
if (!defined(terrainProvider)) {
throw new DeveloperError('terrainProvider is required.');
}
this._terrainProvider = terrainProvider;
if (defined(this._quadtree)) {
this._quadtree.invalidateAllTiles();
}
}
}
});
function sortTileImageryByLayerIndex(a, b) {
var aImagery = a.loadingImagery;
if (!defined(aImagery)) {
aImagery = a.readyImagery;
}
var bImagery = b.loadingImagery;
if (!defined(bImagery)) {
bImagery = b.readyImagery;
}
return aImagery.imageryLayer._layerIndex - bImagery.imageryLayer._layerIndex;
}
function freeVertexArray(vertexArray) {
var indexBuffer = vertexArray.indexBuffer;
vertexArray.destroy();
if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
--indexBuffer.referenceCount;
if (indexBuffer.referenceCount === 0) {
indexBuffer.destroy();
}
}
}
/**
* Called at the beginning of each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
* @param {FrameState} frameState The frame state.
*/
GlobeSurfaceTileProvider.prototype.initialize = function(frameState) {
var imageryLayers = this._imageryLayers;
// update collection: imagery indices, base layers, raise layer show/hide event
imageryLayers._update();
// update each layer for texture reprojection.
imageryLayers.queueReprojectionCommands(frameState);
if (this._layerOrderChanged) {
this._layerOrderChanged = false;
// Sort the TileImagery instances in each tile by the layer index.
this._quadtree.forEachLoadedTile(function(tile) {
tile.data.imagery.sort(sortTileImageryByLayerIndex);
});
}
// Add credits for terrain and imagery providers.
var creditDisplay = frameState.creditDisplay;
if (this._terrainProvider.ready && defined(this._terrainProvider.credit)) {
creditDisplay.addCredit(this._terrainProvider.credit);
}
for (var i = 0, len = imageryLayers.length; i < len; ++i) {
var imageryProvider = imageryLayers.get(i).imageryProvider;
if (imageryProvider.ready && defined(imageryProvider.credit)) {
creditDisplay.addCredit(imageryProvider.credit);
}
}
var vertexArraysToDestroy = this._vertexArraysToDestroy;
var length = vertexArraysToDestroy.length;
for (var j = 0; j < length; ++j) {
freeVertexArray(vertexArraysToDestroy[j]);
}
vertexArraysToDestroy.length = 0;
};
/**
* Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
* or any other functions.
*
* @param {FrameState} frameState The frame state.
*/
GlobeSurfaceTileProvider.prototype.beginUpdate = function(frameState) {
var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
for (var i = 0, len = tilesToRenderByTextureCount.length; i < len; ++i) {
var tiles = tilesToRenderByTextureCount[i];
if (defined(tiles)) {
tiles.length = 0;
}
}
this._usedDrawCommands = 0;
};
/**
* Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame}
* and any other functions.
*
* @param {FrameState} frameState The frame state.
*/
GlobeSurfaceTileProvider.prototype.endUpdate = function(frameState) {
if (!defined(this._renderState)) {
this._renderState = RenderState.fromCache({ // Write color and depth
cull : {
enabled : true
},
depthTest : {
enabled : true,
func : DepthFunction.LESS
}
});
this._blendRenderState = RenderState.fromCache({ // Write color and depth
cull : {
enabled : true
},
depthTest : {
enabled : true,
func : DepthFunction.LESS_OR_EQUAL
},
blending : BlendingState.ALPHA_BLEND
});
}
// Add the tile render commands to the command list, sorted by texture count.
var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
for (var textureCountIndex = 0, textureCountLength = tilesToRenderByTextureCount.length; textureCountIndex < textureCountLength; ++textureCountIndex) {
var tilesToRender = tilesToRenderByTextureCount[textureCountIndex];
if (!defined(tilesToRender)) {
continue;
}
for (var tileIndex = 0, tileLength = tilesToRender.length; tileIndex < tileLength; ++tileIndex) {
addDrawCommandsForTile(this, tilesToRender[tileIndex], frameState);
}
}
};
/**
* Adds draw commands for tiles rendered in the previous frame for a pick pass.
*
* @param {FrameState} frameState The frame state.
*/
GlobeSurfaceTileProvider.prototype.updateForPick = function(frameState) {
if (!defined(this._pickRenderState)) {
this._pickRenderState = RenderState.fromCache({
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
},
depthTest : {
enabled : true
}
});
}
this._usedPickCommands = 0;
var drawCommands = this._drawCommands;
// Add the tile pick commands from the tiles drawn last frame.
for (var i = 0, length = this._usedDrawCommands; i < length; ++i) {
addPickCommandsForTile(this, drawCommands[i], frameState);
}
};
/**
* Cancels any imagery re-projections in the queue.
*/
GlobeSurfaceTileProvider.prototype.cancelReprojections = function() {
this._imageryLayers.cancelReprojections();
};
/**
* Gets the maximum geometric error allowed in a tile at a given level, in meters. This function should not be
* called before {@link GlobeSurfaceTileProvider#ready} returns true.
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error in meters.
*/
GlobeSurfaceTileProvider.prototype.getLevelMaximumGeometricError = function(level) {
return this._terrainProvider.getLevelMaximumGeometricError(level);
};
/**
* Loads, or continues loading, a given tile. This function will continue to be called
* until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}. This function should
* not be called before {@link GlobeSurfaceTileProvider#ready} returns true.
*
* @param {FrameState} frameState The frame state.
* @param {QuadtreeTile} tile The tile to load.
*
* @exception {DeveloperError} loadTile
must not be called before the tile provider is ready.
*/
GlobeSurfaceTileProvider.prototype.loadTile = function(frameState, tile) {
GlobeSurfaceTile.processStateMachine(tile, frameState, this._terrainProvider, this._imageryLayers, this._vertexArraysToDestroy);
};
var boundingSphereScratch = new BoundingSphere();
/**
* Determines the visibility of a given tile. The tile may be fully visible, partially visible, or not
* visible at all. Tiles that are renderable and are at least partially visible will be shown by a call
* to {@link GlobeSurfaceTileProvider#showTileThisFrame}.
*
* @param {QuadtreeTile} tile The tile instance.
* @param {FrameState} frameState The state information about the current frame.
* @param {QuadtreeOccluders} occluders The objects that may occlude this tile.
*
* @returns {Visibility} The visibility of the tile.
*/
GlobeSurfaceTileProvider.prototype.computeTileVisibility = function(tile, frameState, occluders) {
var distance = this.computeDistanceToTile(tile, frameState);
tile._distance = distance;
if (frameState.fog.enabled) {
if (CesiumMath.fog(distance, frameState.fog.density) >= 1.0) {
// Tile is completely in fog so return that it is not visible.
return Visibility.NONE;
}
}
var surfaceTile = tile.data;
var cullingVolume = frameState.cullingVolume;
var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D);
if (frameState.mode !== SceneMode.SCENE3D) {
boundingVolume = boundingSphereScratch;
BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume);
Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
if (frameState.mode === SceneMode.MORPHING) {
boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume);
}
}
var intersection = cullingVolume.computeVisibility(boundingVolume);
if (intersection === Intersect.OUTSIDE) {
return Visibility.NONE;
}
if (frameState.mode === SceneMode.SCENE3D) {
var occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace;
if (!defined(occludeePointInScaledSpace)) {
return intersection;
}
if (occluders.ellipsoid.isScaledSpacePointVisible(occludeePointInScaledSpace)) {
return intersection;
}
return Visibility.NONE;
}
return intersection;
};
var modifiedModelViewScratch = new Matrix4();
var modifiedModelViewProjectionScratch = new Matrix4();
var tileRectangleScratch = new Cartesian4();
var rtcScratch = new Cartesian3();
var centerEyeScratch = new Cartesian3();
var southwestScratch = new Cartesian3();
var northeastScratch = new Cartesian3();
/**
* Shows a specified tile in this frame. The provider can cause the tile to be shown by adding
* render commands to the commandList, or use any other method as appropriate. The tile is not
* expected to be visible next frame as well, unless this method is called next frame, too.
*
* @param {Object} tile The tile instance.
* @param {FrameState} frameState The state information of the current rendering frame.
*/
GlobeSurfaceTileProvider.prototype.showTileThisFrame = function(tile, frameState) {
var readyTextureCount = 0;
var tileImageryCollection = tile.data.imagery;
for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
var tileImagery = tileImageryCollection[i];
if (defined(tileImagery.readyImagery) && tileImagery.readyImagery.imageryLayer.alpha !== 0.0) {
++readyTextureCount;
}
}
var tileSet = this._tilesToRenderByTextureCount[readyTextureCount];
if (!defined(tileSet)) {
tileSet = [];
this._tilesToRenderByTextureCount[readyTextureCount] = tileSet;
}
tileSet.push(tile);
var debug = this._debug;
++debug.tilesRendered;
debug.texturesRendered += readyTextureCount;
};
/**
* Gets the distance from the camera to the closest point on the tile. This is used for level-of-detail selection.
*
* @param {QuadtreeTile} tile The tile instance.
* @param {FrameState} frameState The state information of the current rendering frame.
*
* @returns {Number} The distance from the camera to the closest point on the tile, in meters.
*/
GlobeSurfaceTileProvider.prototype.computeDistanceToTile = function(tile, frameState) {
var surfaceTile = tile.data;
var tileBoundingBox = surfaceTile.tileBoundingBox;
return tileBoundingBox.distanceToCamera(frameState);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see GlobeSurfaceTileProvider#destroy
*/
GlobeSurfaceTileProvider.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* provider = provider && provider();
*
* @see GlobeSurfaceTileProvider#isDestroyed
*/
GlobeSurfaceTileProvider.prototype.destroy = function() {
this._tileProvider = this._tileProvider && this._tileProvider.destroy();
return destroyObject(this);
};
GlobeSurfaceTileProvider.prototype._onLayerAdded = function(layer, index) {
if (layer.show) {
var terrainProvider = this._terrainProvider;
// create TileImagerys for this layer for all previously loaded tiles
this._quadtree.forEachLoadedTile(function(tile) {
if (layer._createTileImagerySkeletons(tile, terrainProvider)) {
tile.state = QuadtreeTileLoadState.LOADING;
}
});
this._layerOrderChanged = true;
}
};
GlobeSurfaceTileProvider.prototype._onLayerRemoved = function(layer, index) {
// destroy TileImagerys for this layer for all previously loaded tiles
this._quadtree.forEachLoadedTile(function(tile) {
var tileImageryCollection = tile.data.imagery;
var startIndex = -1;
var numDestroyed = 0;
for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
var tileImagery = tileImageryCollection[i];
var imagery = tileImagery.loadingImagery;
if (!defined(imagery)) {
imagery = tileImagery.readyImagery;
}
if (imagery.imageryLayer === layer) {
if (startIndex === -1) {
startIndex = i;
}
tileImagery.freeResources();
++numDestroyed;
} else if (startIndex !== -1) {
// iterated past the section of TileImagerys belonging to this layer, no need to continue.
break;
}
}
if (startIndex !== -1) {
tileImageryCollection.splice(startIndex, numDestroyed);
}
});
};
GlobeSurfaceTileProvider.prototype._onLayerMoved = function(layer, newIndex, oldIndex) {
this._layerOrderChanged = true;
};
GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden = function(layer, index, show) {
if (show) {
this._onLayerAdded(layer, index);
} else {
this._onLayerRemoved(layer, index);
}
};
function createTileUniformMap(frameState) {
var uniformMap = {
u_initialColor : function() {
return this.properties.initialColor;
},
u_zoomedOutOceanSpecularIntensity : function() {
return this.properties.zoomedOutOceanSpecularIntensity;
},
u_oceanNormalMap : function() {
return this.properties.oceanNormalMap;
},
u_lightingFadeDistance : function() {
return this.properties.lightingFadeDistance;
},
u_center3D : function() {
return this.properties.center3D;
},
u_tileRectangle : function() {
return this.properties.tileRectangle;
},
u_modifiedModelView : function() {
var viewMatrix = frameState.context.uniformState.view;
var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch);
Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewScratch);
return modifiedModelViewScratch;
},
u_modifiedModelViewProjection : function() {
var viewMatrix = frameState.context.uniformState.view;
var projectionMatrix = frameState.context.uniformState.projection;
var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch);
Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewProjectionScratch);
Matrix4.multiply(projectionMatrix, modifiedModelViewProjectionScratch, modifiedModelViewProjectionScratch);
return modifiedModelViewProjectionScratch;
},
u_dayTextures : function() {
return this.properties.dayTextures;
},
u_dayTextureTranslationAndScale : function() {
return this.properties.dayTextureTranslationAndScale;
},
u_dayTextureTexCoordsRectangle : function() {
return this.properties.dayTextureTexCoordsRectangle;
},
u_dayTextureUseWebMercatorT : function() {
return this.properties.dayTextureUseWebMercatorT;
},
u_dayTextureAlpha : function() {
return this.properties.dayTextureAlpha;
},
u_dayTextureBrightness : function() {
return this.properties.dayTextureBrightness;
},
u_dayTextureContrast : function() {
return this.properties.dayTextureContrast;
},
u_dayTextureHue : function() {
return this.properties.dayTextureHue;
},
u_dayTextureSaturation : function() {
return this.properties.dayTextureSaturation;
},
u_dayTextureOneOverGamma : function() {
return this.properties.dayTextureOneOverGamma;
},
u_dayIntensity : function() {
return this.properties.dayIntensity;
},
u_southAndNorthLatitude : function() {
return this.properties.southAndNorthLatitude;
},
u_southMercatorYAndOneOverHeight : function() {
return this.properties.southMercatorYAndOneOverHeight;
},
u_waterMask : function() {
return this.properties.waterMask;
},
u_waterMaskTranslationAndScale : function() {
return this.properties.waterMaskTranslationAndScale;
},
u_minMaxHeight : function() {
return this.properties.minMaxHeight;
},
u_scaleAndBias : function() {
return this.properties.scaleAndBias;
},
// make a separate object so that changes to the properties are seen on
// derived commands that combine another uniform map with this one.
properties : {
initialColor : new Cartesian4(0.0, 0.0, 0.5, 1.0),
zoomedOutOceanSpecularIntensity : 0.5,
oceanNormalMap : undefined,
lightingFadeDistance : new Cartesian2(6500000.0, 9000000.0),
center3D : undefined,
rtc : new Cartesian3(),
modifiedModelView : new Matrix4(),
tileRectangle : new Cartesian4(),
dayTextures : [],
dayTextureTranslationAndScale : [],
dayTextureTexCoordsRectangle : [],
dayTextureUseWebMercatorT : [],
dayTextureAlpha : [],
dayTextureBrightness : [],
dayTextureContrast : [],
dayTextureHue : [],
dayTextureSaturation : [],
dayTextureOneOverGamma : [],
dayIntensity : 0.0,
southAndNorthLatitude : new Cartesian2(),
southMercatorYAndOneOverHeight : new Cartesian2(),
waterMask : undefined,
waterMaskTranslationAndScale : new Cartesian4(),
minMaxHeight : new Cartesian2(),
scaleAndBias : new Matrix4()
}
};
return uniformMap;
}
function createWireframeVertexArrayIfNecessary(context, provider, tile) {
var surfaceTile = tile.data;
if (defined(surfaceTile.wireframeVertexArray)) {
return;
}
if (!defined(surfaceTile.terrainData) || !defined(surfaceTile.terrainData._mesh)) {
return;
}
surfaceTile.wireframeVertexArray = createWireframeVertexArray(context, surfaceTile.vertexArray, surfaceTile.terrainData._mesh);
}
/**
* Creates a vertex array for wireframe rendering of a terrain tile.
*
* @private
*
* @param {Context} context The context in which to create the vertex array.
* @param {VertexArray} vertexArray The existing, non-wireframe vertex array. The new vertex array
* will share vertex buffers with this existing one.
* @param {TerrainMesh} terrainMesh The terrain mesh containing non-wireframe indices.
* @returns {VertexArray} The vertex array for wireframe rendering.
*/
function createWireframeVertexArray(context, vertexArray, terrainMesh) {
var geometry = {
indices : terrainMesh.indices,
primitiveType : PrimitiveType.TRIANGLES
};
GeometryPipeline.toWireframe(geometry);
var wireframeIndices = geometry.indices;
var wireframeIndexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : wireframeIndices,
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
return new VertexArray({
context : context,
attributes : vertexArray._attributes,
indexBuffer : wireframeIndexBuffer
});
}
var getDebugOrientedBoundingBox;
var getDebugBoundingSphere;
var debugDestroyPrimitive;
(function() {
var instanceOBB = new GeometryInstance({
geometry: BoxOutlineGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0) })
});
var instanceSphere = new GeometryInstance({
geometry: new SphereOutlineGeometry({ radius: 1.0 })
});
var modelMatrix = new Matrix4();
var previousVolume;
var primitive;
function createDebugPrimitive(instance) {
return new Primitive({
geometryInstances : instance,
appearance : new PerInstanceColorAppearance({
translucent : false,
flat : true
}),
asynchronous : false
});
}
getDebugOrientedBoundingBox = function(obb, color) {
if (obb === previousVolume) {
return primitive;
}
debugDestroyPrimitive();
previousVolume = obb;
modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, modelMatrix);
instanceOBB.modelMatrix = modelMatrix;
instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor(color);
primitive = createDebugPrimitive(instanceOBB);
return primitive;
};
getDebugBoundingSphere = function(sphere, color) {
if (sphere === previousVolume) {
return primitive;
}
debugDestroyPrimitive();
previousVolume = sphere;
modelMatrix = Matrix4.fromTranslation(sphere.center, modelMatrix);
modelMatrix = Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix);
instanceSphere.modelMatrix = modelMatrix;
instanceSphere.attributes.color = ColorGeometryInstanceAttribute.fromColor(color);
primitive = createDebugPrimitive(instanceSphere);
return primitive;
};
debugDestroyPrimitive = function() {
if (defined(primitive)) {
primitive.destroy();
primitive = undefined;
previousVolume = undefined;
}
};
})();
var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0);
function addDrawCommandsForTile(tileProvider, tile, frameState) {
var surfaceTile = tile.data;
var maxTextures = ContextLimits.maximumTextureImageUnits;
var waterMaskTexture = surfaceTile.waterMaskTexture;
var showReflectiveOcean = tileProvider.hasWaterMask && defined(waterMaskTexture);
var oceanNormalMap = tileProvider.oceanNormalMap;
var showOceanWaves = showReflectiveOcean && defined(oceanNormalMap);
var hasVertexNormals = tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals;
var enableFog = frameState.fog.enabled;
var castShadows = ShadowMode.castShadows(tileProvider.shadows);
var receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows);
if (showReflectiveOcean) {
--maxTextures;
}
if (showOceanWaves) {
--maxTextures;
}
var rtc = surfaceTile.center;
var encoding = surfaceTile.pickTerrain.mesh.encoding;
// Not used in 3D.
var tileRectangle = tileRectangleScratch;
// Only used for Mercator projections.
var southLatitude = 0.0;
var northLatitude = 0.0;
var southMercatorY = 0.0;
var oneOverMercatorHeight = 0.0;
var useWebMercatorProjection = false;
if (frameState.mode !== SceneMode.SCENE3D) {
var projection = frameState.mapProjection;
var southwest = projection.project(Rectangle.southwest(tile.rectangle), southwestScratch);
var northeast = projection.project(Rectangle.northeast(tile.rectangle), northeastScratch);
tileRectangle.x = southwest.x;
tileRectangle.y = southwest.y;
tileRectangle.z = northeast.x;
tileRectangle.w = northeast.y;
// In 2D and Columbus View, use the center of the tile for RTC rendering.
if (frameState.mode !== SceneMode.MORPHING) {
rtc = rtcScratch;
rtc.x = 0.0;
rtc.y = (tileRectangle.z + tileRectangle.x) * 0.5;
rtc.z = (tileRectangle.w + tileRectangle.y) * 0.5;
tileRectangle.x -= rtc.y;
tileRectangle.y -= rtc.z;
tileRectangle.z -= rtc.y;
tileRectangle.w -= rtc.z;
}
if (frameState.mode === SceneMode.SCENE2D && encoding.quantization === TerrainQuantization.BITS12) {
// In 2D, the texture coordinates of the tile are interpolated over the rectangle to get the position in the vertex shader.
// When the texture coordinates are quantized, error is introduced. This can be seen through the 1px wide cracking
// between the quantized tiles in 2D. To compensate for the error, move the expand the rectangle in each direction by
// half the error amount.
var epsilon = (1.0 / (Math.pow(2.0, 12.0) - 1.0)) * 0.5;
var widthEpsilon = (tileRectangle.z - tileRectangle.x) * epsilon;
var heightEpsilon = (tileRectangle.w - tileRectangle.y) * epsilon;
tileRectangle.x -= widthEpsilon;
tileRectangle.y -= heightEpsilon;
tileRectangle.z += widthEpsilon;
tileRectangle.w += heightEpsilon;
}
if (projection instanceof WebMercatorProjection) {
southLatitude = tile.rectangle.south;
northLatitude = tile.rectangle.north;
southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(southLatitude);
oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(northLatitude) - southMercatorY);
useWebMercatorProjection = true;
}
}
var tileImageryCollection = surfaceTile.imagery;
var imageryIndex = 0;
var imageryLen = tileImageryCollection.length;
var firstPassRenderState = tileProvider._renderState;
var otherPassesRenderState = tileProvider._blendRenderState;
var renderState = firstPassRenderState;
var initialColor = tileProvider._firstPassInitialColor;
var context = frameState.context;
if (!defined(tileProvider._debug.boundingSphereTile)) {
debugDestroyPrimitive();
}
do {
var numberOfDayTextures = 0;
var command;
var uniformMap;
if (tileProvider._drawCommands.length <= tileProvider._usedDrawCommands) {
command = new DrawCommand();
command.owner = tile;
command.cull = false;
command.boundingVolume = new BoundingSphere();
command.orientedBoundingBox = undefined;
uniformMap = createTileUniformMap(frameState);
tileProvider._drawCommands.push(command);
tileProvider._uniformMaps.push(uniformMap);
} else {
command = tileProvider._drawCommands[tileProvider._usedDrawCommands];
uniformMap = tileProvider._uniformMaps[tileProvider._usedDrawCommands];
}
command.owner = tile;
++tileProvider._usedDrawCommands;
if (tile === tileProvider._debug.boundingSphereTile) {
// If a debug primitive already exists for this tile, it will not be
// re-created, to avoid allocation every frame. If it were possible
// to have more than one selected tile, this would have to change.
if (defined(surfaceTile.orientedBoundingBox)) {
getDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(frameState);
} else if (defined(surfaceTile.boundingSphere3D)) {
getDebugBoundingSphere(surfaceTile.boundingSphere3D, Color.RED).update(frameState);
}
}
var uniformMapProperties = uniformMap.properties;
Cartesian4.clone(initialColor, uniformMapProperties.initialColor);
uniformMapProperties.oceanNormalMap = oceanNormalMap;
uniformMapProperties.lightingFadeDistance.x = tileProvider.lightingFadeOutDistance;
uniformMapProperties.lightingFadeDistance.y = tileProvider.lightingFadeInDistance;
uniformMapProperties.zoomedOutOceanSpecularIntensity = tileProvider.zoomedOutOceanSpecularIntensity;
uniformMapProperties.center3D = surfaceTile.center;
Cartesian3.clone(rtc, uniformMapProperties.rtc);
Cartesian4.clone(tileRectangle, uniformMapProperties.tileRectangle);
uniformMapProperties.southAndNorthLatitude.x = southLatitude;
uniformMapProperties.southAndNorthLatitude.y = northLatitude;
uniformMapProperties.southMercatorYAndOneOverHeight.x = southMercatorY;
uniformMapProperties.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight;
// For performance, use fog in the shader only when the tile is in fog.
var applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3;
var applyBrightness = false;
var applyContrast = false;
var applyHue = false;
var applySaturation = false;
var applyGamma = false;
var applyAlpha = false;
while (numberOfDayTextures < maxTextures && imageryIndex < imageryLen) {
var tileImagery = tileImageryCollection[imageryIndex];
var imagery = tileImagery.readyImagery;
++imageryIndex;
if (!defined(imagery) || imagery.imageryLayer.alpha === 0.0) {
continue;
}
var texture = tileImagery.useWebMercatorT ? imagery.textureWebMercator : imagery.texture;
if (!defined(texture)) {
// Our "ready" texture isn't actually ready. This should never happen.
//
// Side note: It IS possible for it to not be in the READY ImageryState, though.
// This can happen when a single imagery tile is shared by two terrain tiles (common)
// and one of them (A) needs a geographic version of the tile because it is near the poles,
// and the other (B) does not. B can and will transition the imagery tile to the READY state
// without reprojecting to geographic. Then, later, A will deem that same tile not-ready-yet
// because it only has the Web Mercator texture, and flip it back to the TRANSITIONING state.
// The imagery tile won't be in the READY state anymore, but it's still READY enough for B's
// purposes.
throw new DeveloperError('readyImagery is not actually ready!');
}
var imageryLayer = imagery.imageryLayer;
if (!defined(tileImagery.textureTranslationAndScale)) {
tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, tileImagery);
}
uniformMapProperties.dayTextures[numberOfDayTextures] = texture;
uniformMapProperties.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale;
uniformMapProperties.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle;
uniformMapProperties.dayTextureUseWebMercatorT[numberOfDayTextures] = tileImagery.useWebMercatorT;
uniformMapProperties.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha;
applyAlpha = applyAlpha || uniformMapProperties.dayTextureAlpha[numberOfDayTextures] !== 1.0;
uniformMapProperties.dayTextureBrightness[numberOfDayTextures] = imageryLayer.brightness;
applyBrightness = applyBrightness || uniformMapProperties.dayTextureBrightness[numberOfDayTextures] !== ImageryLayer.DEFAULT_BRIGHTNESS;
uniformMapProperties.dayTextureContrast[numberOfDayTextures] = imageryLayer.contrast;
applyContrast = applyContrast || uniformMapProperties.dayTextureContrast[numberOfDayTextures] !== ImageryLayer.DEFAULT_CONTRAST;
uniformMapProperties.dayTextureHue[numberOfDayTextures] = imageryLayer.hue;
applyHue = applyHue || uniformMapProperties.dayTextureHue[numberOfDayTextures] !== ImageryLayer.DEFAULT_HUE;
uniformMapProperties.dayTextureSaturation[numberOfDayTextures] = imageryLayer.saturation;
applySaturation = applySaturation || uniformMapProperties.dayTextureSaturation[numberOfDayTextures] !== ImageryLayer.DEFAULT_SATURATION;
uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] = 1.0 / imageryLayer.gamma;
applyGamma = applyGamma || uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] !== 1.0 / ImageryLayer.DEFAULT_GAMMA;
if (defined(imagery.credits)) {
var creditDisplay = frameState.creditDisplay;
var credits = imagery.credits;
for (var creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex) {
creditDisplay.addCredit(credits[creditIndex]);
}
}
++numberOfDayTextures;
}
// trim texture array to the used length so we don't end up using old textures
// which might get destroyed eventually
uniformMapProperties.dayTextures.length = numberOfDayTextures;
uniformMapProperties.waterMask = waterMaskTexture;
Cartesian4.clone(surfaceTile.waterMaskTranslationAndScale, uniformMapProperties.waterMaskTranslationAndScale);
uniformMapProperties.minMaxHeight.x = encoding.minimumHeight;
uniformMapProperties.minMaxHeight.y = encoding.maximumHeight;
Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias);
command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog);
command.castShadows = castShadows;
command.receiveShadows = receiveShadows;
command.renderState = renderState;
command.primitiveType = PrimitiveType.TRIANGLES;
command.vertexArray = surfaceTile.vertexArray;
command.uniformMap = uniformMap;
command.pass = Pass.GLOBE;
if (tileProvider._debug.wireframe) {
createWireframeVertexArrayIfNecessary(context, tileProvider, tile);
if (defined(surfaceTile.wireframeVertexArray)) {
command.vertexArray = surfaceTile.wireframeVertexArray;
command.primitiveType = PrimitiveType.LINES;
}
}
var boundingVolume = command.boundingVolume;
var orientedBoundingBox = command.orientedBoundingBox;
if (frameState.mode !== SceneMode.SCENE3D) {
BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume);
Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
if (frameState.mode === SceneMode.MORPHING) {
boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume);
}
} else {
command.boundingVolume = BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume);
command.orientedBoundingBox = OrientedBoundingBox.clone(surfaceTile.orientedBoundingBox, orientedBoundingBox);
}
frameState.commandList.push(command);
renderState = otherPassesRenderState;
initialColor = otherPassesInitialColor;
} while (imageryIndex < imageryLen);
}
function addPickCommandsForTile(tileProvider, drawCommand, frameState) {
var pickCommand;
if (tileProvider._pickCommands.length <= tileProvider._usedPickCommands) {
pickCommand = new DrawCommand();
pickCommand.cull = false;
tileProvider._pickCommands.push(pickCommand);
} else {
pickCommand = tileProvider._pickCommands[tileProvider._usedPickCommands];
}
++tileProvider._usedPickCommands;
var surfaceTile = drawCommand.owner.data;
var useWebMercatorProjection = frameState.projection instanceof WebMercatorProjection;
pickCommand.shaderProgram = tileProvider._surfaceShaderSet.getPickShaderProgram(frameState, surfaceTile, useWebMercatorProjection);
pickCommand.renderState = tileProvider._pickRenderState;
pickCommand.owner = drawCommand.owner;
pickCommand.primitiveType = drawCommand.primitiveType;
pickCommand.vertexArray = drawCommand.vertexArray;
pickCommand.uniformMap = drawCommand.uniformMap;
pickCommand.boundingVolume = drawCommand.boundingVolume;
pickCommand.orientedBoundingBox = drawCommand.orientedBoundingBox;
pickCommand.pass = drawCommand.pass;
frameState.commandList.push(pickCommand);
}
return GlobeSurfaceTileProvider;
});
/*global define*/
define('Scene/ImageryLayerCollection',[
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Event',
'../Core/Math',
'../Core/Rectangle',
'../ThirdParty/when',
'./ImageryLayer'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Event,
CesiumMath,
Rectangle,
when,
ImageryLayer) {
'use strict';
/**
* An ordered collection of imagery layers.
*
* @alias ImageryLayerCollection
* @constructor
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Adjustment.html|Cesium Sandcastle Imagery Adjustment Demo}
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Layers%20Manipulation.html|Cesium Sandcastle Imagery Manipulation Demo}
*/
function ImageryLayerCollection() {
this._layers = [];
/**
* An event that is raised when a layer is added to the collection. Event handlers are passed the layer that
* was added and the index at which it was added.
* @type {Event}
* @default Event()
*/
this.layerAdded = new Event();
/**
* An event that is raised when a layer is removed from the collection. Event handlers are passed the layer that
* was removed and the index from which it was removed.
* @type {Event}
* @default Event()
*/
this.layerRemoved = new Event();
/**
* An event that is raised when a layer changes position in the collection. Event handlers are passed the layer that
* was moved, its new index after the move, and its old index prior to the move.
* @type {Event}
* @default Event()
*/
this.layerMoved = new Event();
/**
* An event that is raised when a layer is shown or hidden by setting the
* {@link ImageryLayer#show} property. Event handlers are passed a reference to this layer,
* the index of the layer in the collection, and a flag that is true if the layer is now
* shown or false if it is now hidden.
*
* @type {Event}
* @default Event()
*/
this.layerShownOrHidden = new Event();
}
defineProperties(ImageryLayerCollection.prototype, {
/**
* Gets the number of layers in this collection.
* @memberof ImageryLayerCollection.prototype
* @type {Number}
*/
length : {
get : function() {
return this._layers.length;
}
}
});
/**
* Adds a layer to the collection.
*
* @param {ImageryLayer} layer the layer to add.
* @param {Number} [index] the index to add the layer at. If omitted, the layer will
* added on top of all existing layers.
*
* @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of the layers.
*/
ImageryLayerCollection.prototype.add = function(layer, index) {
var hasIndex = defined(index);
if (!defined(layer)) {
throw new DeveloperError('layer is required.');
}
if (hasIndex) {
if (index < 0) {
throw new DeveloperError('index must be greater than or equal to zero.');
} else if (index > this._layers.length) {
throw new DeveloperError('index must be less than or equal to the number of layers.');
}
}
if (!hasIndex) {
index = this._layers.length;
this._layers.push(layer);
} else {
this._layers.splice(index, 0, layer);
}
this._update();
this.layerAdded.raiseEvent(layer, index);
};
/**
* Creates a new layer using the given ImageryProvider and adds it to the collection.
*
* @param {ImageryProvider} imageryProvider the imagery provider to create a new layer for.
* @param {Number} [index] the index to add the layer at. If omitted, the layer will
* added on top of all existing layers.
* @returns {ImageryLayer} The newly created layer.
*/
ImageryLayerCollection.prototype.addImageryProvider = function(imageryProvider, index) {
if (!defined(imageryProvider)) {
throw new DeveloperError('imageryProvider is required.');
}
var layer = new ImageryLayer(imageryProvider);
this.add(layer, index);
return layer;
};
/**
* Removes a layer from this collection, if present.
*
* @param {ImageryLayer} layer The layer to remove.
* @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them.
* @returns {Boolean} true if the layer was in the collection and was removed,
* false if the layer was not in the collection.
*/
ImageryLayerCollection.prototype.remove = function(layer, destroy) {
destroy = defaultValue(destroy, true);
var index = this._layers.indexOf(layer);
if (index !== -1) {
this._layers.splice(index, 1);
this._update();
this.layerRemoved.raiseEvent(layer, index);
if (destroy) {
layer.destroy();
}
return true;
}
return false;
};
/**
* Removes all layers from this collection.
*
* @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them.
*/
ImageryLayerCollection.prototype.removeAll = function(destroy) {
destroy = defaultValue(destroy, true);
var layers = this._layers;
for (var i = 0, len = layers.length; i < len; i++) {
var layer = layers[i];
this.layerRemoved.raiseEvent(layer, i);
if (destroy) {
layer.destroy();
}
}
this._layers = [];
};
/**
* Checks to see if the collection contains a given layer.
*
* @param {ImageryLayer} layer the layer to check for.
*
* @returns {Boolean} true if the collection contains the layer, false otherwise.
*/
ImageryLayerCollection.prototype.contains = function(layer) {
return this.indexOf(layer) !== -1;
};
/**
* Determines the index of a given layer in the collection.
*
* @param {ImageryLayer} layer The layer to find the index of.
*
* @returns {Number} The index of the layer in the collection, or -1 if the layer does not exist in the collection.
*/
ImageryLayerCollection.prototype.indexOf = function(layer) {
return this._layers.indexOf(layer);
};
/**
* Gets a layer by index from the collection.
*
* @param {Number} index the index to retrieve.
*
* @returns {ImageryLayer} The imagery layer at the given index.
*/
ImageryLayerCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.', 'index');
}
return this._layers[index];
};
function getLayerIndex(layers, layer) {
if (!defined(layer)) {
throw new DeveloperError('layer is required.');
}
var index = layers.indexOf(layer);
if (index === -1) {
throw new DeveloperError('layer is not in this collection.');
}
return index;
}
function swapLayers(collection, i, j) {
var arr = collection._layers;
i = CesiumMath.clamp(i, 0, arr.length - 1);
j = CesiumMath.clamp(j, 0, arr.length - 1);
if (i === j) {
return;
}
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
collection._update();
collection.layerMoved.raiseEvent(temp, j, i);
}
/**
* Raises a layer up one position in the collection.
*
* @param {ImageryLayer} layer the layer to move.
*
* @exception {DeveloperError} layer is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
ImageryLayerCollection.prototype.raise = function(layer) {
var index = getLayerIndex(this._layers, layer);
swapLayers(this, index, index + 1);
};
/**
* Lowers a layer down one position in the collection.
*
* @param {ImageryLayer} layer the layer to move.
*
* @exception {DeveloperError} layer is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
ImageryLayerCollection.prototype.lower = function(layer) {
var index = getLayerIndex(this._layers, layer);
swapLayers(this, index, index - 1);
};
/**
* Raises a layer to the top of the collection.
*
* @param {ImageryLayer} layer the layer to move.
*
* @exception {DeveloperError} layer is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
ImageryLayerCollection.prototype.raiseToTop = function(layer) {
var index = getLayerIndex(this._layers, layer);
if (index === this._layers.length - 1) {
return;
}
this._layers.splice(index, 1);
this._layers.push(layer);
this._update();
this.layerMoved.raiseEvent(layer, this._layers.length - 1, index);
};
/**
* Lowers a layer to the bottom of the collection.
*
* @param {ImageryLayer} layer the layer to move.
*
* @exception {DeveloperError} layer is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*/
ImageryLayerCollection.prototype.lowerToBottom = function(layer) {
var index = getLayerIndex(this._layers, layer);
if (index === 0) {
return;
}
this._layers.splice(index, 1);
this._layers.splice(0, 0, layer);
this._update();
this.layerMoved.raiseEvent(layer, 0, index);
};
var applicableRectangleScratch = new Rectangle();
/**
* Asynchronously determines the imagery layer features that are intersected by a pick ray. The intersected imagery
* layer features are found by invoking {@link ImageryProvider#pickFeatures} for each imagery layer tile intersected
* by the pick ray. To compute a pick ray from a location on the screen, use {@link Camera.getPickRay}.
*
* @param {Ray} ray The ray to test for intersection.
* @param {Scene} scene The scene.
* @return {Promise.|undefined} A promise that resolves to an array of features intersected by the pick ray.
* If it can be quickly determined that no features are intersected (for example,
* because no active imagery providers support {@link ImageryProvider#pickFeatures}
* or because the pick ray does not intersect the surface), this function will
* return undefined.
*
* @example
* var pickRay = viewer.camera.getPickRay(windowPosition);
* var featuresPromise = viewer.imageryLayers.pickImageryLayerFeatures(pickRay, viewer.scene);
* if (!Cesium.defined(featuresPromise)) {
* console.log('No features picked.');
* } else {
* Cesium.when(featuresPromise, function(features) {
* // This function is called asynchronously when the list if picked features is available.
* console.log('Number of features: ' + features.length);
* if (features.length > 0) {
* console.log('First feature name: ' + features[0].name);
* }
* });
* }
*/
ImageryLayerCollection.prototype.pickImageryLayerFeatures = function(ray, scene) {
// Find the picked location on the globe.
var pickedPosition = scene.globe.pick(ray, scene);
if (!defined(pickedPosition)) {
return undefined;
}
var pickedLocation = scene.globe.ellipsoid.cartesianToCartographic(pickedPosition);
// Find the terrain tile containing the picked location.
var tilesToRender = scene.globe._surface._tilesToRender;
var pickedTile;
for (var textureIndex = 0; !defined(pickedTile) && textureIndex < tilesToRender.length; ++textureIndex) {
var tile = tilesToRender[textureIndex];
if (Rectangle.contains(tile.rectangle, pickedLocation)) {
pickedTile = tile;
}
}
if (!defined(pickedTile)) {
return undefined;
}
// Pick against all attached imagery tiles containing the pickedLocation.
var imageryTiles = pickedTile.data.imagery;
var promises = [];
var imageryLayers = [];
for (var i = imageryTiles.length - 1; i >= 0; --i) {
var terrainImagery = imageryTiles[i];
var imagery = terrainImagery.readyImagery;
if (!defined(imagery)) {
continue;
}
var provider = imagery.imageryLayer.imageryProvider;
if (!defined(provider.pickFeatures)) {
continue;
}
if (!Rectangle.contains(imagery.rectangle, pickedLocation)) {
continue;
}
// If this imagery came from a parent, it may not be applicable to its entire rectangle.
// Check the textureCoordinateRectangle.
var applicableRectangle = applicableRectangleScratch;
var epsilon = 1 / 1024; // 1/4 of a pixel in a typical 256x256 tile.
applicableRectangle.west = CesiumMath.lerp(pickedTile.rectangle.west, pickedTile.rectangle.east, terrainImagery.textureCoordinateRectangle.x - epsilon);
applicableRectangle.east = CesiumMath.lerp(pickedTile.rectangle.west, pickedTile.rectangle.east, terrainImagery.textureCoordinateRectangle.z + epsilon);
applicableRectangle.south = CesiumMath.lerp(pickedTile.rectangle.south, pickedTile.rectangle.north, terrainImagery.textureCoordinateRectangle.y - epsilon);
applicableRectangle.north = CesiumMath.lerp(pickedTile.rectangle.south, pickedTile.rectangle.north, terrainImagery.textureCoordinateRectangle.w + epsilon);
if (!Rectangle.contains(applicableRectangle, pickedLocation)) {
continue;
}
var promise = provider.pickFeatures(imagery.x, imagery.y, imagery.level, pickedLocation.longitude, pickedLocation.latitude);
if (!defined(promise)) {
continue;
}
promises.push(promise);
imageryLayers.push(imagery.imageryLayer);
}
if (promises.length === 0) {
return undefined;
}
return when.all(promises, function(results) {
var features = [];
for (var resultIndex = 0; resultIndex < results.length; ++resultIndex) {
var result = results[resultIndex];
var image = imageryLayers[resultIndex];
if (defined(result) && result.length > 0) {
for (var featureIndex = 0; featureIndex < result.length; ++featureIndex) {
var feature = result[featureIndex];
feature.imageryLayer = image;
// For features without a position, use the picked location.
if (!defined(feature.position)) {
feature.position = pickedLocation;
}
features.push(feature);
}
}
}
return features;
});
};
/**
* Updates frame state to execute any queued texture re-projections.
*
* @private
*
* @param {FrameState} frameState The frameState.
*/
ImageryLayerCollection.prototype.queueReprojectionCommands = function(frameState) {
var layers = this._layers;
for (var i = 0, len = layers.length; i < len; ++i) {
layers[i].queueReprojectionCommands(frameState);
}
};
/**
* Cancels re-projection commands queued for the next frame.
*
* @private
*/
ImageryLayerCollection.prototype.cancelReprojections = function() {
var layers = this._layers;
for (var i = 0, len = layers.length; i < len; ++i) {
layers[i].cancelReprojections();
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true if this object was destroyed; otherwise, false.
*
* @see ImageryLayerCollection#destroy
*/
ImageryLayerCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by all layers in this collection. Explicitly destroying this
* object allows for deterministic release of WebGL resources, instead of relying on the garbage
* collector.
*
* Once this object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* layerCollection = layerCollection && layerCollection.destroy();
*
* @see ImageryLayerCollection#isDestroyed
*/
ImageryLayerCollection.prototype.destroy = function() {
this.removeAll(true);
return destroyObject(this);
};
ImageryLayerCollection.prototype._update = function() {
var isBaseLayer = true;
var layers = this._layers;
var layersShownOrHidden;
var layer;
for (var i = 0, len = layers.length; i < len; ++i) {
layer = layers[i];
layer._layerIndex = i;
if (layer.show) {
layer._isBaseLayer = isBaseLayer;
isBaseLayer = false;
} else {
layer._isBaseLayer = false;
}
if (layer.show !== layer._show) {
if (defined(layer._show)) {
if (!defined(layersShownOrHidden)) {
layersShownOrHidden = [];
}
layersShownOrHidden.push(layer);
}
layer._show = layer.show;
}
}
if (defined(layersShownOrHidden)) {
for (i = 0, len = layersShownOrHidden.length; i < len; ++i) {
layer = layersShownOrHidden[i];
this.layerShownOrHidden.raiseEvent(layer, layer._layerIndex, layer.show);
}
}
};
return ImageryLayerCollection;
});
/*global define*/
define('Scene/QuadtreeOccluders',[
'../Core/Cartesian3',
'../Core/defineProperties',
'../Core/EllipsoidalOccluder'
], function(
Cartesian3,
defineProperties,
EllipsoidalOccluder) {
'use strict';
/**
* A set of occluders that can be used to test quadtree tiles for occlusion.
*
* @alias QuadtreeOccluders
* @constructor
* @private
*
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid that potentially occludes tiles.
*/
function QuadtreeOccluders(options) {
this._ellipsoid = new EllipsoidalOccluder(options.ellipsoid, Cartesian3.ZERO);
}
defineProperties(QuadtreeOccluders.prototype, {
/**
* Gets the {@link EllipsoidalOccluder} that can be used to determine if a point is
* occluded by an {@link Ellipsoid}.
* @type {EllipsoidalOccluder}
* @memberof QuadtreeOccluders.prototype
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
}
});
return QuadtreeOccluders;
});
/*global define*/
define('Scene/QuadtreeTile',[
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Rectangle',
'./QuadtreeTileLoadState'
], function(
defined,
defineProperties,
DeveloperError,
Rectangle,
QuadtreeTileLoadState) {
'use strict';
/**
* A single tile in a {@link QuadtreePrimitive}.
*
* @alias QuadtreeTile
* @constructor
* @private
*
* @param {Number} options.level The level of the tile in the quadtree.
* @param {Number} options.x The X coordinate of the tile in the quadtree. 0 is the westernmost tile.
* @param {Number} options.y The Y coordinate of the tile in the quadtree. 0 is the northernmost tile.
* @param {TilingScheme} options.tilingScheme The tiling scheme in which this tile exists.
* @param {QuadtreeTile} [options.parent] This tile's parent, or undefined if this is a root tile.
*/
function QuadtreeTile(options) {
if (!defined(options)) {
throw new DeveloperError('options is required.');
}
if (!defined(options.x)) {
throw new DeveloperError('options.x is required.');
} else if (!defined(options.y)) {
throw new DeveloperError('options.y is required.');
} else if (options.x < 0 || options.y < 0) {
throw new DeveloperError('options.x and options.y must be greater than or equal to zero.');
}
if (!defined(options.level)) {
throw new DeveloperError('options.level is required and must be greater than or equal to zero.');
}
if (!defined(options.tilingScheme)) {
throw new DeveloperError('options.tilingScheme is required.');
}
this._tilingScheme = options.tilingScheme;
this._x = options.x;
this._y = options.y;
this._level = options.level;
this._parent = options.parent;
this._rectangle = this._tilingScheme.tileXYToRectangle(this._x, this._y, this._level);
this._southwestChild = undefined;
this._southeastChild = undefined;
this._northwestChild = undefined;
this._northeastChild = undefined;
// QuadtreeTileReplacementQueue gets/sets these private properties.
this._replacementPrevious = undefined;
this._replacementNext = undefined;
// The distance from the camera to this tile, updated when the tile is selected
// for rendering. We can get rid of this if we have a better way to sort by
// distance - for example, by using the natural ordering of a quadtree.
// QuadtreePrimitive gets/sets this private property.
this._distance = 0.0;
this._customData = [];
this._frameUpdated = undefined;
this._frameRendered = undefined;
/**
* Gets or sets the current state of the tile in the tile load pipeline.
* @type {QuadtreeTileLoadState}
* @default {@link QuadtreeTileLoadState.START}
*/
this.state = QuadtreeTileLoadState.START;
/**
* Gets or sets a value indicating whether or not the tile is currently renderable.
* @type {Boolean}
* @default false
*/
this.renderable = false;
/**
* Gets or set a value indicating whether or not the tile was entire upsampled from its
* parent tile. If all four children of a parent tile were upsampled from the parent,
* we will render the parent instead of the children even if the LOD indicates that
* the children would be preferable.
* @type {Boolean}
* @default false
*/
this.upsampledFromParent = false;
/**
* Gets or sets the additional data associated with this tile. The exact content is specific to the
* {@link QuadtreeTileProvider}.
* @type {Object}
* @default undefined
*/
this.data = undefined;
}
/**
* Creates a rectangular set of tiles for level of detail zero, the coarsest, least detailed level.
*
* @memberof QuadtreeTile
*
* @param {TilingScheme} tilingScheme The tiling scheme for which the tiles are to be created.
* @returns {QuadtreeTile[]} An array containing the tiles at level of detail zero, starting with the
* tile in the northwest corner and followed by the tile (if any) to its east.
*/
QuadtreeTile.createLevelZeroTiles = function(tilingScheme) {
if (!defined(tilingScheme)) {
throw new DeveloperError('tilingScheme is required.');
}
var numberOfLevelZeroTilesX = tilingScheme.getNumberOfXTilesAtLevel(0);
var numberOfLevelZeroTilesY = tilingScheme.getNumberOfYTilesAtLevel(0);
var result = new Array(numberOfLevelZeroTilesX * numberOfLevelZeroTilesY);
var index = 0;
for (var y = 0; y < numberOfLevelZeroTilesY; ++y) {
for (var x = 0; x < numberOfLevelZeroTilesX; ++x) {
result[index++] = new QuadtreeTile({
tilingScheme : tilingScheme,
x : x,
y : y,
level : 0
});
}
}
return result;
};
QuadtreeTile.prototype._updateCustomData = function(frameNumber, added, removed) {
var customData = this.customData;
var i;
var data;
var rectangle;
if (defined(added) && defined(removed)) {
customData = customData.filter(function(value) {
return removed.indexOf(value) === -1;
});
this._customData = customData;
rectangle = this._rectangle;
for (i = 0; i < added.length; ++i) {
data = added[i];
if (Rectangle.contains(rectangle, data.positionCartographic)) {
customData.push(data);
}
}
this._frameUpdated = frameNumber;
} else {
// interior or leaf tile, update from parent
var parent = this._parent;
if (defined(parent) && this._frameUpdated !== parent._frameUpdated) {
customData.length = 0;
rectangle = this._rectangle;
var parentCustomData = parent.customData;
for (i = 0; i < parentCustomData.length; ++i) {
data = parentCustomData[i];
if (Rectangle.contains(rectangle, data.positionCartographic)) {
customData.push(data);
}
}
this._frameUpdated = parent._frameUpdated;
}
}
};
defineProperties(QuadtreeTile.prototype, {
/**
* Gets the tiling scheme used to tile the surface.
* @memberof QuadtreeTile.prototype
* @type {TilingScheme}
*/
tilingScheme : {
get : function() {
return this._tilingScheme;
}
},
/**
* Gets the tile X coordinate.
* @memberof QuadtreeTile.prototype
* @type {Number}
*/
x : {
get : function() {
return this._x;
}
},
/**
* Gets the tile Y coordinate.
* @memberof QuadtreeTile.prototype
* @type {Number}
*/
y : {
get : function() {
return this._y;
}
},
/**
* Gets the level-of-detail, where zero is the coarsest, least-detailed.
* @memberof QuadtreeTile.prototype
* @type {Number}
*/
level : {
get : function() {
return this._level;
}
},
/**
* Gets the parent tile of this tile.
* @memberof QuadtreeTile.prototype
* @type {QuadtreeTile}
*/
parent : {
get : function() {
return this._parent;
}
},
/**
* Gets the cartographic rectangle of the tile, with north, south, east and
* west properties in radians.
* @memberof QuadtreeTile.prototype
* @type {Rectangle}
*/
rectangle : {
get : function() {
return this._rectangle;
}
},
/**
* An array of tiles that is at the next level of the tile tree.
* @memberof QuadtreeTile.prototype
* @type {QuadtreeTile[]}
*/
children : {
get : function() {
return [this.northwestChild, this.northeastChild, this.southwestChild, this.southeastChild];
}
},
/**
* Gets the southwest child tile.
* @memberof QuadtreeTile.prototype
* @type {QuadtreeTile}
*/
southwestChild : {
get : function() {
if (!defined(this._southwestChild)) {
this._southwestChild = new QuadtreeTile({
tilingScheme : this.tilingScheme,
x : this.x * 2,
y : this.y * 2 + 1,
level : this.level + 1,
parent : this
});
}
return this._southwestChild;
}
},
/**
* Gets the southeast child tile.
* @memberof QuadtreeTile.prototype
* @type {QuadtreeTile}
*/
southeastChild : {
get : function() {
if (!defined(this._southeastChild)) {
this._southeastChild = new QuadtreeTile({
tilingScheme : this.tilingScheme,
x : this.x * 2 + 1,
y : this.y * 2 + 1,
level : this.level + 1,
parent : this
});
}
return this._southeastChild;
}
},
/**
* Gets the northwest child tile.
* @memberof QuadtreeTile.prototype
* @type {QuadtreeTile}
*/
northwestChild : {
get : function() {
if (!defined(this._northwestChild)) {
this._northwestChild = new QuadtreeTile({
tilingScheme : this.tilingScheme,
x : this.x * 2,
y : this.y * 2,
level : this.level + 1,
parent : this
});
}
return this._northwestChild;
}
},
/**
* Gets the northeast child tile.
* @memberof QuadtreeTile.prototype
* @type {QuadtreeTile}
*/
northeastChild : {
get : function() {
if (!defined(this._northeastChild)) {
this._northeastChild = new QuadtreeTile({
tilingScheme : this.tilingScheme,
x : this.x * 2 + 1,
y : this.y * 2,
level : this.level + 1,
parent : this
});
}
return this._northeastChild;
}
},
/**
* An array of objects associated with this tile.
* @memberof QuadtreeTile.prototype
* @type {Array}
*/
customData : {
get : function() {
return this._customData;
}
},
/**
* Gets a value indicating whether or not this tile needs further loading.
* This property will return true if the {@link QuadtreeTile#state} is
* START
or LOADING
.
* @memberof QuadtreeTile.prototype
* @type {Boolean}
*/
needsLoading : {
get : function() {
return this.state < QuadtreeTileLoadState.DONE;
}
},
/**
* Gets a value indicating whether or not this tile is eligible to be unloaded.
* Typically, a tile is ineligible to be unloaded while an asynchronous operation,
* such as a request for data, is in progress on it. A tile will never be
* unloaded while it is needed for rendering, regardless of the value of this
* property. If {@link QuadtreeTile#data} is defined and has an
* eligibleForUnloading
property, the value of that property is returned.
* Otherwise, this property returns true.
* @memberof QuadtreeTile.prototype
* @type {Boolean}
*/
eligibleForUnloading : {
get : function() {
var result = true;
if (defined(this.data)) {
result = this.data.eligibleForUnloading;
if (!defined(result)) {
result = true;
}
}
return result;
}
}
});
/**
* Frees the resources associated with this tile and returns it to the START
* {@link QuadtreeTileLoadState}. If the {@link QuadtreeTile#data} property is defined and it
* has a freeResources
method, the method will be invoked.
*
* @memberof QuadtreeTile
*/
QuadtreeTile.prototype.freeResources = function() {
this.state = QuadtreeTileLoadState.START;
this.renderable = false;
this.upsampledFromParent = false;
if (defined(this.data) && defined(this.data.freeResources)) {
this.data.freeResources();
}
freeTile(this._southwestChild);
this._southwestChild = undefined;
freeTile(this._southeastChild);
this._southeastChild = undefined;
freeTile(this._northwestChild);
this._northwestChild = undefined;
freeTile(this._northeastChild);
this._northeastChild = undefined;
};
function freeTile(tile) {
if (defined(tile)) {
tile.freeResources();
}
}
return QuadtreeTile;
});
/*global define*/
define('Scene/TileReplacementQueue',[
'../Core/defined'
], function(
defined) {
'use strict';
/**
* A priority queue of tiles to be replaced, if necessary, to make room for new tiles. The queue
* is implemented as a linked list.
*
* @alias TileReplacementQueue
* @private
*/
function TileReplacementQueue() {
this.head = undefined;
this.tail = undefined;
this.count = 0;
this._lastBeforeStartOfFrame = undefined;
}
/**
* Marks the start of the render frame. Tiles before (closer to the head) this tile in the
* list were used last frame and must not be unloaded.
*/
TileReplacementQueue.prototype.markStartOfRenderFrame = function() {
this._lastBeforeStartOfFrame = this.head;
};
/**
* Reduces the size of the queue to a specified size by unloading the least-recently used
* tiles. Tiles that were used last frame will not be unloaded, even if that puts the number
* of tiles above the specified maximum.
*
* @param {Number} maximumTiles The maximum number of tiles in the queue.
*/
TileReplacementQueue.prototype.trimTiles = function(maximumTiles) {
var tileToTrim = this.tail;
var keepTrimming = true;
while (keepTrimming &&
defined(this._lastBeforeStartOfFrame) &&
this.count > maximumTiles &&
defined(tileToTrim)) {
// Stop trimming after we process the last tile not used in the
// current frame.
keepTrimming = tileToTrim !== this._lastBeforeStartOfFrame;
var previous = tileToTrim.replacementPrevious;
if (tileToTrim.eligibleForUnloading) {
tileToTrim.freeResources();
remove(this, tileToTrim);
}
tileToTrim = previous;
}
};
function remove(tileReplacementQueue, item) {
var previous = item.replacementPrevious;
var next = item.replacementNext;
if (item === tileReplacementQueue._lastBeforeStartOfFrame) {
tileReplacementQueue._lastBeforeStartOfFrame = next;
}
if (item === tileReplacementQueue.head) {
tileReplacementQueue.head = next;
} else {
previous.replacementNext = next;
}
if (item === tileReplacementQueue.tail) {
tileReplacementQueue.tail = previous;
} else {
next.replacementPrevious = previous;
}
item.replacementPrevious = undefined;
item.replacementNext = undefined;
--tileReplacementQueue.count;
}
/**
* Marks a tile as rendered this frame and moves it before the first tile that was not rendered
* this frame.
*
* @param {TileReplacementQueue} item The tile that was rendered.
*/
TileReplacementQueue.prototype.markTileRendered = function(item) {
var head = this.head;
if (head === item) {
if (item === this._lastBeforeStartOfFrame) {
this._lastBeforeStartOfFrame = item.replacementNext;
}
return;
}
++this.count;
if (!defined(head)) {
// no other tiles in the list
item.replacementPrevious = undefined;
item.replacementNext = undefined;
this.head = item;
this.tail = item;
return;
}
if (defined(item.replacementPrevious) || defined(item.replacementNext)) {
// tile already in the list, remove from its current location
remove(this, item);
}
item.replacementPrevious = undefined;
item.replacementNext = head;
head.replacementPrevious = item;
this.head = item;
};
return TileReplacementQueue;
});
/*global define*/
define('Scene/QuadtreePrimitive',[
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/getTimestamp',
'../Core/Math',
'../Core/Queue',
'../Core/Ray',
'../Core/Rectangle',
'../Core/Visibility',
'./QuadtreeOccluders',
'./QuadtreeTile',
'./QuadtreeTileLoadState',
'./SceneMode',
'./TileReplacementQueue'
], function(
Cartesian3,
Cartographic,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
getTimestamp,
CesiumMath,
Queue,
Ray,
Rectangle,
Visibility,
QuadtreeOccluders,
QuadtreeTile,
QuadtreeTileLoadState,
SceneMode,
TileReplacementQueue) {
'use strict';
/**
* Renders massive sets of data by utilizing level-of-detail and culling. The globe surface is divided into
* a quadtree of tiles with large, low-detail tiles at the root and small, high-detail tiles at the leaves.
* The set of tiles to render is selected by projecting an estimate of the geometric error in a tile onto
* the screen to estimate screen-space error, in pixels, which must be below a user-specified threshold.
* The actual content of the tiles is arbitrary and is specified using a {@link QuadtreeTileProvider}.
*
* @alias QuadtreePrimitive
* @constructor
* @private
*
* @param {QuadtreeTileProvider} options.tileProvider The tile provider that loads, renders, and estimates
* the distance to individual tiles.
* @param {Number} [options.maximumScreenSpaceError=2] The maximum screen-space error, in pixels, that is allowed.
* A higher maximum error will render fewer tiles and improve performance, while a lower
* value will improve visual quality.
* @param {Number} [options.tileCacheSize=100] The maximum number of tiles that will be retained in the tile cache.
* Note that tiles will never be unloaded if they were used for rendering the last
* frame, so the actual number of resident tiles may be higher. The value of
* this property will not affect visual quality.
*/
function QuadtreePrimitive(options) {
if (!defined(options) || !defined(options.tileProvider)) {
throw new DeveloperError('options.tileProvider is required.');
}
if (defined(options.tileProvider.quadtree)) {
throw new DeveloperError('A QuadtreeTileProvider can only be used with a single QuadtreePrimitive');
}
this._tileProvider = options.tileProvider;
this._tileProvider.quadtree = this;
this._debug = {
enableDebugOutput : false,
maxDepth : 0,
tilesVisited : 0,
tilesCulled : 0,
tilesRendered : 0,
tilesWaitingForChildren : 0,
lastMaxDepth : -1,
lastTilesVisited : -1,
lastTilesCulled : -1,
lastTilesRendered : -1,
lastTilesWaitingForChildren : -1,
suspendLodUpdate : false
};
var tilingScheme = this._tileProvider.tilingScheme;
var ellipsoid = tilingScheme.ellipsoid;
this._tilesToRender = [];
this._tileLoadQueueHigh = []; // high priority tiles are preventing refinement
this._tileLoadQueueMedium = []; // medium priority tiles are being rendered
this._tileLoadQueueLow = []; // low priority tiles were refined past or are non-visible parts of quads.
this._tileReplacementQueue = new TileReplacementQueue();
this._levelZeroTiles = undefined;
this._levelZeroTilesReady = false;
this._loadQueueTimeSlice = 5.0;
this._addHeightCallbacks = [];
this._removeHeightCallbacks = [];
this._tileToUpdateHeights = [];
this._lastTileIndex = 0;
this._updateHeightsTimeSlice = 2.0;
/**
* Gets or sets the maximum screen-space error, in pixels, that is allowed.
* A higher maximum error will render fewer tiles and improve performance, while a lower
* value will improve visual quality.
* @type {Number}
* @default 2
*/
this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 2);
/**
* Gets or sets the maximum number of tiles that will be retained in the tile cache.
* Note that tiles will never be unloaded if they were used for rendering the last
* frame, so the actual number of resident tiles may be higher. The value of
* this property will not affect visual quality.
* @type {Number}
* @default 100
*/
this.tileCacheSize = defaultValue(options.tileCacheSize, 100);
this._occluders = new QuadtreeOccluders({
ellipsoid : ellipsoid
});
this._tileLoadProgressEvent = new Event();
this._lastTileLoadQueueLength = 0;
}
defineProperties(QuadtreePrimitive.prototype, {
/**
* Gets the provider of {@link QuadtreeTile} instances for this quadtree.
* @type {QuadtreeTile}
* @memberof QuadtreePrimitive.prototype
*/
tileProvider : {
get : function() {
return this._tileProvider;
}
},
/**
* Gets an event that's raised when the length of the tile load queue has changed since the last render frame. When the load queue is empty,
* all terrain and imagery for the current view have been loaded. The event passes the new length of the tile load queue.
*
* @memberof QuadtreePrimitive.prototype
* @type {Event}
*/
tileLoadProgressEvent : {
get : function() {
return this._tileLoadProgressEvent;
}
}
});
/**
* Invalidates and frees all the tiles in the quadtree. The tiles must be reloaded
* before they can be displayed.
*
* @memberof QuadtreePrimitive
*/
QuadtreePrimitive.prototype.invalidateAllTiles = function() {
// Clear the replacement queue
var replacementQueue = this._tileReplacementQueue;
replacementQueue.head = undefined;
replacementQueue.tail = undefined;
replacementQueue.count = 0;
// Free and recreate the level zero tiles.
var levelZeroTiles = this._levelZeroTiles;
if (defined(levelZeroTiles)) {
for (var i = 0; i < levelZeroTiles.length; ++i) {
var tile = levelZeroTiles[i];
var customData = tile.customData;
var customDataLength = customData.length;
for (var j = 0; j < customDataLength; ++j) {
var data = customData[j];
data.level = 0;
this._addHeightCallbacks.push(data);
}
levelZeroTiles[i].freeResources();
}
}
this._levelZeroTiles = undefined;
this._tileProvider.cancelReprojections();
};
/**
* Invokes a specified function for each {@link QuadtreeTile} that is partially
* or completely loaded.
*
* @param {Function} tileFunction The function to invoke for each loaded tile. The
* function is passed a reference to the tile as its only parameter.
*/
QuadtreePrimitive.prototype.forEachLoadedTile = function(tileFunction) {
var tile = this._tileReplacementQueue.head;
while (defined(tile)) {
if (tile.state !== QuadtreeTileLoadState.START) {
tileFunction(tile);
}
tile = tile.replacementNext;
}
};
/**
* Invokes a specified function for each {@link QuadtreeTile} that was rendered
* in the most recent frame.
*
* @param {Function} tileFunction The function to invoke for each rendered tile. The
* function is passed a reference to the tile as its only parameter.
*/
QuadtreePrimitive.prototype.forEachRenderedTile = function(tileFunction) {
var tilesRendered = this._tilesToRender;
for (var i = 0, len = tilesRendered.length; i < len; ++i) {
tileFunction(tilesRendered[i]);
}
};
/**
* Calls the callback when a new tile is rendered that contains the given cartographic. The only parameter
* is the cartesian position on the tile.
*
* @param {Cartographic} cartographic The cartographic position.
* @param {Function} callback The function to be called when a new tile is loaded containing cartographic.
* @returns {Function} The function to remove this callback from the quadtree.
*/
QuadtreePrimitive.prototype.updateHeight = function(cartographic, callback) {
var primitive = this;
var object = {
positionOnEllipsoidSurface : undefined,
positionCartographic : cartographic,
level : -1,
callback : callback
};
object.removeFunc = function() {
var addedCallbacks = primitive._addHeightCallbacks;
var length = addedCallbacks.length;
for (var i = 0; i < length; ++i) {
if (addedCallbacks[i] === object) {
addedCallbacks.splice(i, 1);
break;
}
}
primitive._removeHeightCallbacks.push(object);
};
primitive._addHeightCallbacks.push(object);
return object.removeFunc;
};
/**
* @private
*/
QuadtreePrimitive.prototype.beginFrame = function(frameState) {
var passes = frameState.passes;
if (!passes.render) {
return;
}
// Gets commands for any texture re-projections and updates the credit display
this._tileProvider.initialize(frameState);
var debug = this._debug;
if (debug.suspendLodUpdate) {
return;
}
debug.maxDepth = 0;
debug.tilesVisited = 0;
debug.tilesCulled = 0;
debug.tilesRendered = 0;
debug.tilesWaitingForChildren = 0;
this._tileLoadQueueHigh.length = 0;
this._tileLoadQueueMedium.length = 0;
this._tileLoadQueueLow.length = 0;
this._tileReplacementQueue.markStartOfRenderFrame();
};
/**
* @private
*/
QuadtreePrimitive.prototype.update = function(frameState) {
var passes = frameState.passes;
if (passes.render) {
this._tileProvider.beginUpdate(frameState);
selectTilesForRendering(this, frameState);
createRenderCommandsForSelectedTiles(this, frameState);
this._tileProvider.endUpdate(frameState);
}
if (passes.pick && this._tilesToRender.length > 0) {
this._tileProvider.updateForPick(frameState);
}
};
/**
* @private
*/
QuadtreePrimitive.prototype.endFrame = function(frameState) {
var passes = frameState.passes;
if (!passes.render || frameState.mode === SceneMode.MORPHING) {
// Only process the load queue for a single pass.
// Don't process the load queue or update heights during the morph flights.
return;
}
// Load/create resources for terrain and imagery. Prepare texture re-projections for the next frame.
processTileLoadQueue(this, frameState);
updateHeights(this, frameState);
var debug = this._debug;
if (debug.suspendLodUpdate) {
return;
}
if (debug.enableDebugOutput) {
if (debug.tilesVisited !== debug.lastTilesVisited ||
debug.tilesRendered !== debug.lastTilesRendered ||
debug.tilesCulled !== debug.lastTilesCulled ||
debug.maxDepth !== debug.lastMaxDepth ||
debug.tilesWaitingForChildren !== debug.lastTilesWaitingForChildren) {
console.log('Visited ' + debug.tilesVisited + ', Rendered: ' + debug.tilesRendered + ', Culled: ' + debug.tilesCulled + ', Max Depth: ' + debug.maxDepth + ', Waiting for children: ' + debug.tilesWaitingForChildren);
debug.lastTilesVisited = debug.tilesVisited;
debug.lastTilesRendered = debug.tilesRendered;
debug.lastTilesCulled = debug.tilesCulled;
debug.lastMaxDepth = debug.maxDepth;
debug.lastTilesWaitingForChildren = debug.tilesWaitingForChildren;
}
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @memberof QuadtreePrimitive
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see QuadtreePrimitive#destroy
*/
QuadtreePrimitive.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @memberof QuadtreePrimitive
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* primitive = primitive && primitive.destroy();
*
* @see QuadtreePrimitive#isDestroyed
*/
QuadtreePrimitive.prototype.destroy = function() {
this._tileProvider = this._tileProvider && this._tileProvider.destroy();
};
var comparisonPoint;
var centerScratch = new Cartographic();
function compareDistanceToPoint(a, b) {
var center = Rectangle.center(a.rectangle, centerScratch);
var alon = center.longitude - comparisonPoint.longitude;
var alat = center.latitude - comparisonPoint.latitude;
center = Rectangle.center(b.rectangle, centerScratch);
var blon = center.longitude - comparisonPoint.longitude;
var blat = center.latitude - comparisonPoint.latitude;
return (alon * alon + alat * alat) - (blon * blon + blat * blat);
}
function selectTilesForRendering(primitive, frameState) {
var debug = primitive._debug;
if (debug.suspendLodUpdate) {
return;
}
var i;
var len;
// Clear the render list.
var tilesToRender = primitive._tilesToRender;
tilesToRender.length = 0;
// We can't render anything before the level zero tiles exist.
if (!defined(primitive._levelZeroTiles)) {
if (primitive._tileProvider.ready) {
var tilingScheme = primitive._tileProvider.tilingScheme;
primitive._levelZeroTiles = QuadtreeTile.createLevelZeroTiles(tilingScheme);
} else {
// Nothing to do until the provider is ready.
return;
}
}
primitive._occluders.ellipsoid.cameraPosition = frameState.camera.positionWC;
var tileProvider = primitive._tileProvider;
var occluders = primitive._occluders;
var tile;
var levelZeroTiles = primitive._levelZeroTiles;
// Sort the level zero tiles by the distance from the center to the camera.
// The level zero tiles aren't necessarily a nice neat quad, so we can use the
// quadtree ordering we use elsewhere in the tree
comparisonPoint = frameState.camera.positionCartographic;
levelZeroTiles.sort(compareDistanceToPoint);
var customDataAdded = primitive._addHeightCallbacks;
var customDataRemoved = primitive._removeHeightCallbacks;
var frameNumber = frameState.frameNumber;
if (customDataAdded.length > 0 || customDataRemoved.length > 0) {
for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
tile = levelZeroTiles[i];
tile._updateCustomData(frameNumber, customDataAdded, customDataRemoved);
}
customDataAdded.length = 0;
customDataRemoved.length = 0;
}
// Our goal with load ordering is to first load all of the tiles we need to
// render the current scene at full detail. Loading any other tiles is just
// a form of prefetching, and we need not do it at all (other concerns aside). This
// simple and obvious statement gets more complicated when we realize that, because
// we don't have bounding volumes for the entire terrain tile pyramid, we don't
// precisely know which tiles we need to render the scene at full detail, until we do
// some loading.
//
// So our load priority is (from high to low):
// 1. Tiles that we _would_ render, except that they're not sufficiently loaded yet.
// Ideally this would only include tiles that we've already determined to be visible,
// but since we don't have reliable visibility information until a tile is loaded,
// and because we (currently) must have all children in a quad renderable before we
// can refine, this pretty much means tiles we'd like to refine to, regardless of
// visibility. (high)
// 2. Tiles that we're rendering. (medium)
// 3. All other tiles. (low)
//
// Within each priority group, tiles should be loaded in approximate near-to-far order,
// but currently they're just loaded in our traversal order which makes no guarantees
// about depth ordering.
// Traverse in depth-first, near-to-far order.
for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
tile = levelZeroTiles[i];
primitive._tileReplacementQueue.markTileRendered(tile);
if (!tile.renderable) {
if (tile.needsLoading) {
primitive._tileLoadQueueHigh.push(tile);
}
++debug.tilesWaitingForChildren;
} else if (tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) {
visitTile(primitive, frameState, tile);
} else {
if (tile.needsLoading) {
primitive._tileLoadQueueLow.push(tile);
}
++debug.tilesCulled;
}
}
raiseTileLoadProgressEvent(primitive);
}
function visitTile(primitive, frameState, tile) {
var debug = primitive._debug;
++debug.tilesVisited;
primitive._tileReplacementQueue.markTileRendered(tile);
tile._updateCustomData(frameState.frameNumber);
if (tile.level > debug.maxDepth) {
debug.maxDepth = tile.level;
}
if (screenSpaceError(primitive, frameState, tile) < primitive.maximumScreenSpaceError) {
// This tile meets SSE requirements, so render it.
if (tile.needsLoading) {
// Rendered tile meeting SSE loads with medium priority.
primitive._tileLoadQueueMedium.push(tile);
}
addTileToRenderList(primitive, tile);
return;
}
var southwestChild = tile.southwestChild;
var southeastChild = tile.southeastChild;
var northwestChild = tile.northwestChild;
var northeastChild = tile.northeastChild;
var allAreRenderable = southwestChild.renderable && southeastChild.renderable &&
northwestChild.renderable && northeastChild.renderable;
var allAreUpsampled = southwestChild.upsampledFromParent && southeastChild.upsampledFromParent &&
northwestChild.upsampledFromParent && northeastChild.upsampledFromParent;
if (allAreRenderable) {
if (allAreUpsampled) {
// No point in rendering the children because they're all upsampled. Render this tile instead.
addTileToRenderList(primitive, tile);
// Load the children even though we're (currently) not going to render them.
// A tile that is "upsampled only" right now might change its tune once it does more loading.
// A tile that is upsampled now and forever should also be done loading, so no harm done.
queueChildLoadNearToFar(primitive, frameState.camera.positionCartographic, southwestChild, southeastChild, northwestChild, northeastChild);
if (tile.needsLoading) {
// Rendered tile that's not waiting on children loads with medium priority.
primitive._tileLoadQueueMedium.push(tile);
}
} else {
// SSE is not good enough and children are loaded, so refine.
// No need to add the children to the load queue because they'll be added (if necessary) when they're visited.
visitVisibleChildrenNearToFar(primitive, southwestChild, southeastChild, northwestChild, northeastChild, frameState);
if (tile.needsLoading) {
// Tile is not rendered, so load it with low priority.
primitive._tileLoadQueueLow.push(tile);
}
}
} else {
// We'd like to refine but can't because not all of our children are renderable. Load the refinement blockers with high priority and
// render this tile in the meantime.
queueChildLoadNearToFar(primitive, frameState.camera.positionCartographic, southwestChild, southeastChild, northwestChild, northeastChild);
addTileToRenderList(primitive, tile);
if (tile.needsLoading) {
// We will refine this tile when it's possible, so load this tile only with low priority.
primitive._tileLoadQueueLow.push(tile);
}
}
}
function queueChildLoadNearToFar(primitive, cameraPosition, southwest, southeast, northwest, northeast) {
if (cameraPosition.longitude < southwest.east) {
if (cameraPosition.latitude < southwest.north) {
// Camera in southwest quadrant
queueChildTileLoad(primitive, southwest);
queueChildTileLoad(primitive, southeast);
queueChildTileLoad(primitive, northwest);
queueChildTileLoad(primitive, northeast);
} else {
// Camera in northwest quadrant
queueChildTileLoad(primitive, northwest);
queueChildTileLoad(primitive, southwest);
queueChildTileLoad(primitive, northeast);
queueChildTileLoad(primitive, southeast);
}
} else {
if (cameraPosition.latitude < southwest.north) {
// Camera southeast quadrant
queueChildTileLoad(primitive, southeast);
queueChildTileLoad(primitive, southwest);
queueChildTileLoad(primitive, northeast);
queueChildTileLoad(primitive, northwest);
} else {
// Camera in northeast quadrant
queueChildTileLoad(primitive, northeast);
queueChildTileLoad(primitive, northwest);
queueChildTileLoad(primitive, southeast);
queueChildTileLoad(primitive, southwest);
}
}
}
function queueChildTileLoad(primitive, childTile) {
primitive._tileReplacementQueue.markTileRendered(childTile);
if (childTile.needsLoading) {
if (childTile.renderable) {
primitive._tileLoadQueueLow.push(childTile);
} else {
// A tile blocking refine loads with high priority
primitive._tileLoadQueueHigh.push(childTile);
}
}
}
function visitVisibleChildrenNearToFar(primitive, southwest, southeast, northwest, northeast, frameState) {
var cameraPosition = frameState.camera.positionCartographic;
var tileProvider = primitive._tileProvider;
var occluders = primitive._occluders;
if (cameraPosition.longitude < southwest.rectangle.east) {
if (cameraPosition.latitude < southwest.rectangle.north) {
// Camera in southwest quadrant
visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
} else {
// Camera in northwest quadrant
visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
}
} else {
if (cameraPosition.latitude < southwest.rectangle.north) {
// Camera southeast quadrant
visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
} else {
// Camera in northeast quadrant
visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
}
}
}
function visitIfVisible(primitive, tile, tileProvider, frameState, occluders) {
if (tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) {
visitTile(primitive, frameState, tile);
} else {
++primitive._debug.tilesCulled;
primitive._tileReplacementQueue.markTileRendered(tile);
}
}
/**
* Checks if the load queue length has changed since the last time we raised a queue change event - if so, raises
* a new one.
*/
function raiseTileLoadProgressEvent(primitive) {
var currentLoadQueueLength = primitive._tileLoadQueueHigh.length + primitive._tileLoadQueueMedium.length + primitive._tileLoadQueueLow.length;
if (currentLoadQueueLength !== primitive._lastTileLoadQueueLength) {
primitive._tileLoadProgressEvent.raiseEvent(currentLoadQueueLength);
primitive._lastTileLoadQueueLength = currentLoadQueueLength;
}
}
function screenSpaceError(primitive, frameState, tile) {
if (frameState.mode === SceneMode.SCENE2D) {
return screenSpaceError2D(primitive, frameState, tile);
}
var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
var distance = tile._distance;
var height = frameState.context.drawingBufferHeight;
var sseDenominator = frameState.camera.frustum.sseDenominator;
var error = (maxGeometricError * height) / (distance * sseDenominator);
if (frameState.fog.enabled) {
error = error - CesiumMath.fog(distance, frameState.fog.density) * frameState.fog.sse;
}
return error;
}
function screenSpaceError2D(primitive, frameState, tile) {
var camera = frameState.camera;
var frustum = camera.frustum;
var context = frameState.context;
var width = context.drawingBufferWidth;
var height = context.drawingBufferHeight;
var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height);
return maxGeometricError / pixelSize;
}
function addTileToRenderList(primitive, tile) {
primitive._tilesToRender.push(tile);
++primitive._debug.tilesRendered;
}
function processTileLoadQueue(primitive, frameState) {
var tileLoadQueueHigh = primitive._tileLoadQueueHigh;
var tileLoadQueueMedium = primitive._tileLoadQueueMedium;
var tileLoadQueueLow = primitive._tileLoadQueueLow;
var tileProvider = primitive._tileProvider;
if (tileLoadQueueHigh.length === 0 && tileLoadQueueMedium.length === 0 && tileLoadQueueLow.length === 0) {
return;
}
// Remove any tiles that were not used this frame beyond the number
// we're allowed to keep.
primitive._tileReplacementQueue.trimTiles(primitive.tileCacheSize);
var endTime = getTimestamp() + primitive._loadQueueTimeSlice;
processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, tileLoadQueueHigh);
processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, tileLoadQueueMedium);
processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, tileLoadQueueLow);
}
function processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, loadQueue) {
for (var i = 0, len = loadQueue.length; i < len && getTimestamp() < endTime; ++i) {
var tile = loadQueue[i];
primitive._tileReplacementQueue.markTileRendered(tile);
tileProvider.loadTile(frameState, tile);
}
}
var scratchRay = new Ray();
var scratchCartographic = new Cartographic();
var scratchPosition = new Cartesian3();
function updateHeights(primitive, frameState) {
var tilesToUpdateHeights = primitive._tileToUpdateHeights;
var terrainProvider = primitive._tileProvider.terrainProvider;
var startTime = getTimestamp();
var timeSlice = primitive._updateHeightsTimeSlice;
var endTime = startTime + timeSlice;
var mode = frameState.mode;
var projection = frameState.mapProjection;
var ellipsoid = projection.ellipsoid;
while (tilesToUpdateHeights.length > 0) {
var tile = tilesToUpdateHeights[0];
var customData = tile.customData;
var customDataLength = customData.length;
var timeSliceMax = false;
for (var i = primitive._lastTileIndex; i < customDataLength; ++i) {
var data = customData[i];
if (tile.level > data.level) {
if (!defined(data.positionOnEllipsoidSurface)) {
// cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
data.positionOnEllipsoidSurface = Cartesian3.fromRadians(data.positionCartographic.longitude, data.positionCartographic.latitude, 0.0, ellipsoid);
}
if (mode === SceneMode.SCENE3D) {
var surfaceNormal = ellipsoid.geodeticSurfaceNormal(data.positionOnEllipsoidSurface, scratchRay.direction);
// compute origin point
// Try to find the intersection point between the surface normal and z-axis.
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(data.positionOnEllipsoidSurface, 11500.0, scratchRay.origin);
// Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
if (!defined(rayOrigin)) {
// intersection point is outside the ellipsoid, try other value
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0);
// multiply by the *positive* value of the magnitude
var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchPosition);
Cartesian3.subtract(data.positionOnEllipsoidSurface, vectorToMinimumPoint, scratchRay.origin);
}
} else {
Cartographic.clone(data.positionCartographic, scratchCartographic);
// minimum height for the terrain set, need to get this information from the terrain provider
scratchCartographic.height = -11500.0;
projection.project(scratchCartographic, scratchPosition);
Cartesian3.fromElements(scratchPosition.z, scratchPosition.x, scratchPosition.y, scratchPosition);
Cartesian3.clone(scratchPosition, scratchRay.origin);
Cartesian3.clone(Cartesian3.UNIT_X, scratchRay.direction);
}
var position = tile.data.pick(scratchRay, mode, projection, false, scratchPosition);
if (defined(position)) {
data.callback(position);
}
data.level = tile.level;
} else if (tile.level === data.level) {
var children = tile.children;
var childrenLength = children.length;
var child;
for (var j = 0; j < childrenLength; ++j) {
child = children[j];
if (Rectangle.contains(child.rectangle, data.positionCartographic)) {
break;
}
}
var tileDataAvailable = terrainProvider.getTileDataAvailable(child.x, child.y, child.level);
var parentTile = tile.parent;
if ((defined(tileDataAvailable) && !tileDataAvailable) ||
(defined(parentTile) && defined(parentTile.data) && defined(parentTile.data.terrainData) &&
!parentTile.data.terrainData.isChildAvailable(parentTile.x, parentTile.y, child.x, child.y))) {
data.removeFunc();
}
}
if (getTimestamp() >= endTime) {
timeSliceMax = true;
break;
}
}
if (timeSliceMax) {
primitive._lastTileIndex = i;
break;
} else {
primitive._lastTileIndex = 0;
tilesToUpdateHeights.shift();
}
}
}
function createRenderCommandsForSelectedTiles(primitive, frameState) {
var tileProvider = primitive._tileProvider;
var tilesToRender = primitive._tilesToRender;
var tilesToUpdateHeights = primitive._tileToUpdateHeights;
for (var i = 0, len = tilesToRender.length; i < len; ++i) {
var tile = tilesToRender[i];
tileProvider.showTileThisFrame(tile, frameState);
if (tile._frameRendered !== frameState.frameNumber - 1) {
tilesToUpdateHeights.push(tile);
}
tile._frameRendered = frameState.frameNumber;
}
}
return QuadtreePrimitive;
});
/*global define*/
define('Scene/Globe',[
'../Core/BoundingSphere',
'../Core/buildModuleUrl',
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/EllipsoidTerrainProvider',
'../Core/Event',
'../Core/IntersectionTests',
'../Core/loadImage',
'../Core/Math',
'../Core/Ray',
'../Core/Rectangle',
'../Renderer/ShaderSource',
'../Renderer/Texture',
'../Shaders/GlobeFS',
'../Shaders/GlobeVS',
'../Shaders/GroundAtmosphere',
'../ThirdParty/when',
'./GlobeSurfaceShaderSet',
'./GlobeSurfaceTileProvider',
'./ImageryLayerCollection',
'./QuadtreePrimitive',
'./SceneMode',
'./ShadowMode'
], function(
BoundingSphere,
buildModuleUrl,
Cartesian3,
Cartographic,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
Ellipsoid,
EllipsoidTerrainProvider,
Event,
IntersectionTests,
loadImage,
CesiumMath,
Ray,
Rectangle,
ShaderSource,
Texture,
GlobeFS,
GlobeVS,
GroundAtmosphere,
when,
GlobeSurfaceShaderSet,
GlobeSurfaceTileProvider,
ImageryLayerCollection,
QuadtreePrimitive,
SceneMode,
ShadowMode) {
'use strict';
/**
* The globe rendered in the scene, including its terrain ({@link Globe#terrainProvider})
* and imagery layers ({@link Globe#imageryLayers}). Access the globe using {@link Scene#globe}.
*
* @alias Globe
* @constructor
*
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the
* globe.
*/
function Globe(ellipsoid) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var terrainProvider = new EllipsoidTerrainProvider({
ellipsoid : ellipsoid
});
var imageryLayerCollection = new ImageryLayerCollection();
this._ellipsoid = ellipsoid;
this._imageryLayerCollection = imageryLayerCollection;
this._surfaceShaderSet = new GlobeSurfaceShaderSet();
this._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
sources : [GroundAtmosphere, GlobeVS]
});
this._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
sources : [GlobeFS]
});
this._surface = new QuadtreePrimitive({
tileProvider : new GlobeSurfaceTileProvider({
terrainProvider : terrainProvider,
imageryLayers : imageryLayerCollection,
surfaceShaderSet : this._surfaceShaderSet
})
});
this._terrainProvider = terrainProvider;
this._terrainProviderChanged = new Event();
/**
* Determines if the globe will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = true;
/**
* The normal map to use for rendering waves in the ocean. Setting this property will
* only have an effect if the configured terrain provider includes a water mask.
*
* @type {String}
* @default buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg')
*/
this.oceanNormalMapUrl = buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg');
this._oceanNormalMapUrl = undefined;
/**
* The maximum screen-space error used to drive level-of-detail refinement. Higher
* values will provide better performance but lower visual quality.
*
* @type {Number}
* @default 2
*/
this.maximumScreenSpaceError = 2;
/**
* The size of the terrain tile cache, expressed as a number of tiles. Any additional
* tiles beyond this number will be freed, as long as they aren't needed for rendering
* this frame. A larger number will consume more memory but will show detail faster
* when, for example, zooming out and then back in.
*
* @type {Number}
* @default 100
*/
this.tileCacheSize = 100;
/**
* Enable lighting the globe with the sun as a light source.
*
* @type {Boolean}
* @default false
*/
this.enableLighting = false;
/**
* The distance where everything becomes lit. This only takes effect
* when enableLighting
is true
.
*
* @type {Number}
* @default 6500000.0
*/
this.lightingFadeOutDistance = 6500000.0;
/**
* The distance where lighting resumes. This only takes effect
* when enableLighting
is true
.
*
* @type {Number}
* @default 9000000.0
*/
this.lightingFadeInDistance = 9000000.0;
/**
* True if an animated wave effect should be shown in areas of the globe
* covered by water; otherwise, false. This property is ignored if the
* terrainProvider
does not provide a water mask.
*
* @type {Boolean}
* @default true
*/
this.showWaterEffect = true;
/**
* True if primitives such as billboards, polylines, labels, etc. should be depth-tested
* against the terrain surface, or false if such primitives should always be drawn on top
* of terrain unless they're on the opposite side of the globe. The disadvantage of depth
* testing primitives against terrain is that slight numerical noise or terrain level-of-detail
* switched can sometimes make a primitive that should be on the surface disappear underneath it.
*
* @type {Boolean}
* @default false
*
*/
this.depthTestAgainstTerrain = false;
/**
* Determines whether the globe casts or receives shadows from each light source. Setting the globe
* to cast shadows may impact performance since the terrain is rendered again from the light's perspective.
* Currently only terrain that is in view casts shadows. By default the globe does not cast shadows.
*
* @type {ShadowMode}
* @default ShadowMode.RECEIVE_ONLY
*/
this.shadows = ShadowMode.RECEIVE_ONLY;
this._oceanNormalMap = undefined;
this._zoomedOutOceanSpecularIntensity = 0.5;
}
defineProperties(Globe.prototype, {
/**
* Gets an ellipsoid describing the shape of this globe.
* @memberof Globe.prototype
* @type {Ellipsoid}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
},
/**
* Gets the collection of image layers that will be rendered on this globe.
* @memberof Globe.prototype
* @type {ImageryLayerCollection}
*/
imageryLayers : {
get : function() {
return this._imageryLayerCollection;
}
},
/**
* Gets or sets the color of the globe when no imagery is available.
* @memberof Globe.prototype
* @type {Color}
*/
baseColor : {
get : function() {
return this._surface.tileProvider.baseColor;
},
set : function(value) {
this._surface.tileProvider.baseColor = value;
}
},
/**
* The terrain provider providing surface geometry for this globe.
* @type {TerrainProvider}
*
* @memberof Globe.prototype
* @type {TerrainProvider}
*
*/
terrainProvider : {
get : function() {
return this._terrainProvider;
},
set : function(value) {
if (value !== this._terrainProvider) {
this._terrainProvider = value;
this._terrainProviderChanged.raiseEvent(value);
}
}
},
/**
* Gets an event that's raised when the terrain provider is changed
*
* @memberof Globe.prototype
* @type {Event}
* @readonly
*/
terrainProviderChanged : {
get: function() {
return this._terrainProviderChanged;
}
},
/**
* Gets an event that's raised when the length of the tile load queue has changed since the last render frame. When the load queue is empty,
* all terrain and imagery for the current view have been loaded. The event passes the new length of the tile load queue.
*
* @memberof Globe.prototype
* @type {Event}
*/
tileLoadProgressEvent : {
get: function() {
return this._surface.tileLoadProgressEvent;
}
}
});
function createComparePickTileFunction(rayOrigin) {
return function(a, b) {
var aDist = BoundingSphere.distanceSquaredTo(a.pickBoundingSphere, rayOrigin);
var bDist = BoundingSphere.distanceSquaredTo(b.pickBoundingSphere, rayOrigin);
return aDist - bDist;
};
}
var scratchArray = [];
var scratchSphereIntersectionResult = {
start : 0.0,
stop : 0.0
};
/**
* Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates.
*
* @param {Ray} ray The ray to test for intersection.
* @param {Scene} scene The scene.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3|undefined} The intersection or undefined
if none was found.
*
* @example
* // find intersection of ray through a pixel and the globe
* var ray = viewer.camera.getPickRay(windowCoordinates);
* var intersection = globe.pick(ray, scene);
*/
Globe.prototype.pick = function(ray, scene, result) {
if (!defined(ray)) {
throw new DeveloperError('ray is required');
}
if (!defined(scene)) {
throw new DeveloperError('scene is required');
}
var mode = scene.mode;
var projection = scene.mapProjection;
var sphereIntersections = scratchArray;
sphereIntersections.length = 0;
var tilesToRender = this._surface._tilesToRender;
var length = tilesToRender.length;
var tile;
var i;
for (i = 0; i < length; ++i) {
tile = tilesToRender[i];
var tileData = tile.data;
if (!defined(tileData)) {
continue;
}
var boundingVolume = tileData.pickBoundingSphere;
if (mode !== SceneMode.SCENE3D) {
BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, projection, tileData.minimumHeight, tileData.maximumHeight, boundingVolume);
Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
} else {
BoundingSphere.clone(tileData.boundingSphere3D, boundingVolume);
}
var boundingSphereIntersection = IntersectionTests.raySphere(ray, boundingVolume, scratchSphereIntersectionResult);
if (defined(boundingSphereIntersection)) {
sphereIntersections.push(tileData);
}
}
sphereIntersections.sort(createComparePickTileFunction(ray.origin));
var intersection;
length = sphereIntersections.length;
for (i = 0; i < length; ++i) {
intersection = sphereIntersections[i].pick(ray, scene.mode, scene.mapProjection, true, result);
if (defined(intersection)) {
break;
}
}
return intersection;
};
var scratchGetHeightCartesian = new Cartesian3();
var scratchGetHeightIntersection = new Cartesian3();
var scratchGetHeightCartographic = new Cartographic();
var scratchGetHeightRay = new Ray();
function tileIfContainsCartographic(tile, cartographic) {
return Rectangle.contains(tile.rectangle, cartographic) ? tile : undefined;
}
/**
* Get the height of the surface at a given cartographic.
*
* @param {Cartographic} cartographic The cartographic for which to find the height.
* @returns {Number|undefined} The height of the cartographic or undefined if it could not be found.
*/
Globe.prototype.getHeight = function(cartographic) {
if (!defined(cartographic)) {
throw new DeveloperError('cartographic is required');
}
var levelZeroTiles = this._surface._levelZeroTiles;
if (!defined(levelZeroTiles)) {
return;
}
var tile;
var i;
var length = levelZeroTiles.length;
for (i = 0; i < length; ++i) {
tile = levelZeroTiles[i];
if (Rectangle.contains(tile.rectangle, cartographic)) {
break;
}
}
if (!defined(tile) || !Rectangle.contains(tile.rectangle, cartographic)) {
return undefined;
}
while (tile.renderable) {
tile = tileIfContainsCartographic(tile.southwestChild, cartographic) ||
tileIfContainsCartographic(tile.southeastChild, cartographic) ||
tileIfContainsCartographic(tile.northwestChild, cartographic) ||
tile.northeastChild;
}
while (defined(tile) && (!defined(tile.data) || !defined(tile.data.pickTerrain))) {
tile = tile.parent;
}
if (!defined(tile)) {
return undefined;
}
var ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid;
//cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
var cartesian = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0, ellipsoid, scratchGetHeightCartesian);
var ray = scratchGetHeightRay;
var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesian, ray.direction);
// Try to find the intersection point between the surface normal and z-axis.
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian, 11500.0, ray.origin);
// Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
if (!defined(rayOrigin)) {
// intersection point is outside the ellipsoid, try other value
// minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0);
// multiply by the *positive* value of the magnitude
var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchGetHeightIntersection);
Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin);
}
var intersection = tile.data.pick(ray, undefined, undefined, false, scratchGetHeightIntersection);
if (!defined(intersection)) {
return undefined;
}
return ellipsoid.cartesianToCartographic(intersection, scratchGetHeightCartographic).height;
};
/**
* @private
*/
Globe.prototype.beginFrame = function(frameState) {
if (!this.show) {
return;
}
var surface = this._surface;
var tileProvider = surface.tileProvider;
var terrainProvider = this.terrainProvider;
var hasWaterMask = this.showWaterEffect && terrainProvider.ready && terrainProvider.hasWaterMask;
if (hasWaterMask && this.oceanNormalMapUrl !== this._oceanNormalMapUrl) {
// url changed, load new normal map asynchronously
var oceanNormalMapUrl = this.oceanNormalMapUrl;
this._oceanNormalMapUrl = oceanNormalMapUrl;
if (defined(oceanNormalMapUrl)) {
var that = this;
when(loadImage(oceanNormalMapUrl), function(image) {
if (oceanNormalMapUrl !== that.oceanNormalMapUrl) {
// url changed while we were loading
return;
}
that._oceanNormalMap = that._oceanNormalMap && that._oceanNormalMap.destroy();
that._oceanNormalMap = new Texture({
context : frameState.context,
source : image
});
});
} else {
this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
}
}
var mode = frameState.mode;
var pass = frameState.passes;
if (pass.render) {
// Don't show the ocean specular highlights when zoomed out in 2D and Columbus View.
if (mode === SceneMode.SCENE3D) {
this._zoomedOutOceanSpecularIntensity = 0.5;
} else {
this._zoomedOutOceanSpecularIntensity = 0.0;
}
surface.maximumScreenSpaceError = this.maximumScreenSpaceError;
surface.tileCacheSize = this.tileCacheSize;
tileProvider.terrainProvider = this.terrainProvider;
tileProvider.lightingFadeOutDistance = this.lightingFadeOutDistance;
tileProvider.lightingFadeInDistance = this.lightingFadeInDistance;
tileProvider.zoomedOutOceanSpecularIntensity = this._zoomedOutOceanSpecularIntensity;
tileProvider.hasWaterMask = hasWaterMask;
tileProvider.oceanNormalMap = this._oceanNormalMap;
tileProvider.enableLighting = this.enableLighting;
tileProvider.shadows = this.shadows;
surface.beginFrame(frameState);
}
};
/**
* @private
*/
Globe.prototype.update = function(frameState) {
if (!this.show) {
return;
}
var surface = this._surface;
var pass = frameState.passes;
if (pass.render) {
surface.update(frameState);
}
if (pass.pick) {
surface.update(frameState);
}
};
/**
* @private
*/
Globe.prototype.endFrame = function(frameState) {
if (!this.show) {
return;
}
if (frameState.passes.render) {
this._surface.endFrame(frameState);
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see Globe#destroy
*/
Globe.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* globe = globe && globe.destroy();
*
* @see Globe#isDestroyed
*/
Globe.prototype.destroy = function() {
this._surfaceShaderSet = this._surfaceShaderSet && this._surfaceShaderSet.destroy();
this._surface = this._surface && this._surface.destroy();
this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
return destroyObject(this);
};
return Globe;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/PassThrough',[],function() {
'use strict';
return "uniform sampler2D u_texture;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n\
}\n\
";
});
/*global define*/
define('Scene/GlobeDepth',[
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/PixelFormat',
'../Renderer/ClearCommand',
'../Renderer/Framebuffer',
'../Renderer/PixelDatatype',
'../Renderer/RenderState',
'../Renderer/Texture',
'../Shaders/PostProcessFilters/PassThrough'
], function(
BoundingRectangle,
Color,
defined,
destroyObject,
PixelFormat,
ClearCommand,
Framebuffer,
PixelDatatype,
RenderState,
Texture,
PassThrough) {
'use strict';
/**
* @private
*/
function GlobeDepth() {
this._colorTexture = undefined;
this._depthStencilTexture = undefined;
this._globeDepthTexture = undefined;
this.framebuffer = undefined;
this._copyDepthFramebuffer = undefined;
this._clearColorCommand = undefined;
this._copyColorCommand = undefined;
this._copyDepthCommand = undefined;
this._viewport = new BoundingRectangle();
this._rs = undefined;
this._debugGlobeDepthViewportCommand = undefined;
}
function executeDebugGlobeDepth(globeDepth, context, passState) {
if (!defined(globeDepth._debugGlobeDepthViewportCommand)) {
var fs =
'uniform sampler2D u_texture;\n' +
'varying vec2 v_textureCoordinates;\n' +
'void main()\n' +
'{\n' +
' float z_window = czm_unpackDepth(texture2D(u_texture, v_textureCoordinates));\n' +
' float n_range = czm_depthRange.near;\n' +
' float f_range = czm_depthRange.far;\n' +
' float z_ndc = (2.0 * z_window - n_range - f_range) / (f_range - n_range);\n' +
' float scale = pow(z_ndc * 0.5 + 0.5, 8.0);\n' +
' gl_FragColor = vec4(mix(vec3(0.0), vec3(1.0), scale), 1.0);\n' +
'}\n';
globeDepth._debugGlobeDepthViewportCommand = context.createViewportQuadCommand(fs, {
uniformMap : {
u_texture : function() {
return globeDepth._globeDepthTexture;
}
},
owner : globeDepth
});
}
globeDepth._debugGlobeDepthViewportCommand.execute(context, passState);
}
function destroyTextures(globeDepth) {
globeDepth._colorTexture = globeDepth._colorTexture && !globeDepth._colorTexture.isDestroyed() && globeDepth._colorTexture.destroy();
globeDepth._depthStencilTexture = globeDepth._depthStencilTexture && !globeDepth._depthStencilTexture.isDestroyed() && globeDepth._depthStencilTexture.destroy();
globeDepth._globeDepthTexture = globeDepth._globeDepthTexture && !globeDepth._globeDepthTexture.isDestroyed() && globeDepth._globeDepthTexture.destroy();
}
function destroyFramebuffers(globeDepth) {
globeDepth.framebuffer = globeDepth.framebuffer && !globeDepth.framebuffer.isDestroyed() && globeDepth.framebuffer.destroy();
globeDepth._copyDepthFramebuffer = globeDepth._copyDepthFramebuffer && !globeDepth._copyDepthFramebuffer.isDestroyed() && globeDepth._copyDepthFramebuffer.destroy();
}
function createTextures(globeDepth, context, width, height) {
globeDepth._colorTexture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE
});
globeDepth._depthStencilTexture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.DEPTH_STENCIL,
pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8
});
globeDepth._globeDepthTexture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE
});
}
function createFramebuffers(globeDepth, context, width, height) {
globeDepth.framebuffer = new Framebuffer({
context : context,
colorTextures : [globeDepth._colorTexture],
depthStencilTexture : globeDepth._depthStencilTexture,
destroyAttachments : false
});
globeDepth._copyDepthFramebuffer = new Framebuffer({
context : context,
colorTextures : [globeDepth._globeDepthTexture],
destroyAttachments : false
});
}
function updateFramebuffers(globeDepth, context, width, height) {
var colorTexture = globeDepth._colorTexture;
var textureChanged = !defined(colorTexture) || colorTexture.width !== width || colorTexture.height !== height;
if (!defined(globeDepth.framebuffer) || textureChanged) {
destroyTextures(globeDepth);
destroyFramebuffers(globeDepth);
createTextures(globeDepth, context, width, height);
createFramebuffers(globeDepth, context, width, height);
}
}
function updateCopyCommands(globeDepth, context, width, height) {
globeDepth._viewport.width = width;
globeDepth._viewport.height = height;
if (!defined(globeDepth._rs) || !BoundingRectangle.equals(globeDepth._viewport, globeDepth._rs.viewport)) {
globeDepth._rs = RenderState.fromCache({
viewport : globeDepth._viewport
});
}
if (!defined(globeDepth._copyDepthCommand)) {
var fs =
'uniform sampler2D u_texture;\n' +
'varying vec2 v_textureCoordinates;\n' +
'void main()\n' +
'{\n' +
' gl_FragColor = czm_packDepth(texture2D(u_texture, v_textureCoordinates).r);\n' +
'}\n';
globeDepth._copyDepthCommand = context.createViewportQuadCommand(fs, {
uniformMap : {
u_texture : function() {
return globeDepth._depthStencilTexture;
}
},
owner : globeDepth
});
}
globeDepth._copyDepthCommand.framebuffer = globeDepth._copyDepthFramebuffer;
if (!defined(globeDepth._copyColorCommand)) {
globeDepth._copyColorCommand = context.createViewportQuadCommand(PassThrough, {
uniformMap : {
u_texture : function() {
return globeDepth._colorTexture;
}
},
owner : globeDepth
});
}
globeDepth._copyDepthCommand.renderState = globeDepth._rs;
globeDepth._copyColorCommand.renderState = globeDepth._rs;
if (!defined(globeDepth._clearColorCommand)) {
globeDepth._clearColorCommand = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0),
stencil : 0.0,
owner : globeDepth
});
}
globeDepth._clearColorCommand.framebuffer = globeDepth.framebuffer;
}
GlobeDepth.prototype.executeDebugGlobeDepth = function(context, passState) {
executeDebugGlobeDepth(this, context, passState);
};
GlobeDepth.prototype.update = function(context) {
var width = context.drawingBufferWidth;
var height = context.drawingBufferHeight;
updateFramebuffers(this, context, width, height);
updateCopyCommands(this, context, width, height);
context.uniformState.globeDepthTexture = undefined;
};
GlobeDepth.prototype.executeCopyDepth = function(context, passState) {
if (defined(this._copyDepthCommand)) {
this._copyDepthCommand.execute(context, passState);
context.uniformState.globeDepthTexture = this._globeDepthTexture;
}
};
GlobeDepth.prototype.executeCopyColor = function(context, passState) {
if (defined(this._copyColorCommand)) {
this._copyColorCommand.execute(context, passState);
}
};
GlobeDepth.prototype.clear = function(context, passState, clearColor) {
var clear = this._clearColorCommand;
if (defined(clear)) {
Color.clone(clearColor, clear.color);
clear.execute(context, passState);
}
};
GlobeDepth.prototype.isDestroyed = function() {
return false;
};
GlobeDepth.prototype.destroy = function() {
destroyTextures(this);
destroyFramebuffers(this);
if (defined(this._copyColorCommand)) {
this._copyColorCommand.shaderProgram = this._copyColorCommand.shaderProgram.destroy();
}
if (defined(this._copyDepthCommand)) {
this._copyDepthCommand.shaderProgram = this._copyDepthCommand.shaderProgram.destroy();
}
var command = this._debugGlobeDepthViewportCommand;
if (defined(command)) {
command.shaderProgram = command.shaderProgram.destroy();
}
return destroyObject(this);
};
return GlobeDepth;
});
/*global define*/
define('Scene/GoogleEarthImageryProvider',[
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/GeographicTilingScheme',
'../Core/loadText',
'../Core/Rectangle',
'../Core/RuntimeError',
'../Core/TileProviderError',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/when',
'./ImageryProvider'
], function(
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
GeographicTilingScheme,
loadText,
Rectangle,
RuntimeError,
TileProviderError,
WebMercatorTilingScheme,
when,
ImageryProvider) {
'use strict';
/**
* Provides tiled imagery using the Google Earth Imagery API.
*
* Notes: This imagery provider does not work with the public Google Earth servers. It works with the
* Google Earth Enterprise Server.
*
* By default the Google Earth Enterprise server does not set the
* {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} headers. You can either
* use a proxy server which adds these headers, or in the /opt/google/gehttpd/conf/gehttpd.conf
* and add the 'Header set Access-Control-Allow-Origin "*"' option to the '<Directory />' and
* '<Directory "/opt/google/gehttpd/htdocs">' directives.
*
* @alias GoogleEarthImageryProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The url of the Google Earth server hosting the imagery.
* @param {Number} options.channel The channel (id) to be used when requesting data from the server.
* The channel number can be found by looking at the json file located at:
* earth.localdomain/default_map/query?request=Json&vars=geeServerDefs The /default_map path may
* differ depending on your Google Earth Enterprise server configuration. Look for the "id" that
* is associated with a "ImageryMaps" requestType. There may be more than one id available.
* Example:
* {
* layers: [
* {
* id: 1002,
* requestType: "ImageryMaps"
* },
* {
* id: 1007,
* requestType: "VectorMapsRaster"
* }
* ]
* }
* @param {String} [options.path="/default_map"] The path of the Google Earth server hosting the imagery.
* @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the Google Earth
* Enterprise server, or undefined if there is no limit.
* @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
* is invalid and should be discarded. To ensure that no tiles are discarded, construct and pass
* a {@link NeverTileDiscardPolicy} for this parameter.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {Proxy} [options.proxy] A proxy to use for requests. This object is
* expected to have a getURL function which returns the proxied URL, if needed.
*
* @exception {RuntimeError} Could not find layer with channel (id) of options.channel
.
* @exception {RuntimeError} Could not find a version in channel (id) options.channel
.
* @exception {RuntimeError} Unsupported projection data.projection
.
*
* @see ArcGisMapServerImageryProvider
* @see BingMapsImageryProvider
* @see createOpenStreetMapImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
* @see UrlTemplateImageryProvider
*
*
* @example
* var google = new Cesium.GoogleEarthImageryProvider({
* url : 'https://earth.localdomain',
* channel : 1008
* });
*
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
*/
function GoogleEarthImageryProvider(options) {
options = defaultValue(options, {});
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
if (!defined(options.channel)) {
throw new DeveloperError('options.channel is required.');
}
this._url = options.url;
this._path = defaultValue(options.path, '/default_map');
this._tileDiscardPolicy = options.tileDiscardPolicy;
this._proxy = options.proxy;
this._channel = options.channel;
this._requestType = 'ImageryMaps';
this._credit = new Credit('Google Imagery', GoogleEarthImageryProvider._logoData, 'http://www.google.com/enterprise/mapsearth/products/earthenterprise.html');
/**
* The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider.
* By default, this is set to 1.9. Changing this value after creating an {@link ImageryLayer} for this provider will have
* no effect. Instead, set the layer's {@link ImageryLayer#gamma} property.
*
* @type {Number}
* @default 1.9
*/
this.defaultGamma = 1.9;
this._tilingScheme = undefined;
this._version = undefined;
this._tileWidth = 256;
this._tileHeight = 256;
this._maximumLevel = options.maximumLevel;
this._imageUrlTemplate = this._url + this._path + '/query?request={request}&channel={channel}&version={version}&x={x}&y={y}&z={zoom}';
this._errorEvent = new Event();
this._ready = false;
this._readyPromise = when.defer();
var metadataUrl = this._url + this._path + '/query?request=Json&vars=geeServerDefs&is2d=t';
var that = this;
var metadataError;
function metadataSuccess(text) {
var data;
// The Google Earth server sends malformed JSON data currently...
try {
// First, try parsing it like normal in case a future version sends correctly formatted JSON
data = JSON.parse(text);
} catch(e) {
// Quote object strings manually, then try parsing again
data = JSON.parse(text.replace(/([\[\{,])[\n\r ]*([A-Za-z0-9]+)[\n\r ]*:/g, '$1"$2":'));
}
var layer;
for(var i = 0; i < data.layers.length; i++) {
if(data.layers[i].id === that._channel) {
layer = data.layers[i];
break;
}
}
var message;
if(!defined(layer)) {
message = 'Could not find layer with channel (id) of ' + that._channel + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
throw new RuntimeError(message);
}
if(!defined(layer.version)) {
message = 'Could not find a version in channel (id) ' + that._channel + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
throw new RuntimeError(message);
}
that._version = layer.version;
if(defined(data.projection) && data.projection === 'flat') {
that._tilingScheme = new GeographicTilingScheme({
numberOfLevelZeroTilesX : 2,
numberOfLevelZeroTilesY : 2,
rectangle: new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI),
ellipsoid : options.ellipsoid
});
// Default to mercator projection when projection is undefined
} else if(!defined(data.projection) || data.projection === 'mercator') {
that._tilingScheme = new WebMercatorTilingScheme({
numberOfLevelZeroTilesX : 2,
numberOfLevelZeroTilesY : 2,
ellipsoid : options.ellipsoid
});
} else {
message = 'Unsupported projection ' + data.projection + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
throw new RuntimeError(message);
}
that._imageUrlTemplate = that._imageUrlTemplate.replace('{request}', that._requestType)
.replace('{channel}', that._channel).replace('{version}', that._version);
that._ready = true;
that._readyPromise.resolve(true);
TileProviderError.handleSuccess(metadataError);
}
function metadataFailure(e) {
var message = 'An error occurred while accessing ' + metadataUrl + '.';
metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
that._readyPromise.reject(new RuntimeError(message));
}
function requestMetadata() {
var url = (!defined(that._proxy)) ? metadataUrl : that._proxy.getURL(metadataUrl);
var metadata = loadText(url);
when(metadata, metadataSuccess, metadataFailure);
}
requestMetadata();
}
defineProperties(GoogleEarthImageryProvider.prototype, {
/**
* Gets the URL of the Google Earth MapServer.
* @memberof GoogleEarthImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the url path of the data on the Google Earth server.
* @memberof GoogleEarthImageryProvider.prototype
* @type {String}
* @readonly
*/
path : {
get : function() {
return this._path;
}
},
/**
* Gets the proxy used by this provider.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._proxy;
}
},
/**
* Gets the imagery channel (id) currently being used.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Number}
* @readonly
*/
channel : {
get : function() {
return this._channel;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
}
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight: {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
}
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
}
return this._maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
}
return 0;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
}
return this._tilingScheme;
}
},
/**
* Gets the version of the data used by this provider. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Number}
* @readonly
*/
version : {
get : function() {
if (!this._ready) {
throw new DeveloperError('version must not be called before the imagery provider is ready.');
}
return this._version;
}
},
/**
* Gets the type of data that is being requested from the provider. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {String}
* @readonly
*/
requestType : {
get : function() {
if (!this._ready) {
throw new DeveloperError('requestType must not be called before the imagery provider is ready.');
}
return this._requestType;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
if (!this._ready) {
throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
}
return this._tilingScheme.rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
}
return this._tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link GoogleEarthImageryProvider#ready} returns true.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof GoogleEarthImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return true;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
GoogleEarthImageryProvider.prototype.getTileCredits = function(x, y, level) {
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link GoogleEarthImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
GoogleEarthImageryProvider.prototype.requestImage = function(x, y, level) {
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
}
var url = buildImageUrl(this, x, y, level);
return ImageryProvider.loadImage(this, url);
};
/**
* Picking features is not currently supported by this imagery provider, so this function simply returns
* undefined.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
GoogleEarthImageryProvider.prototype.pickFeatures = function() {
return undefined;
};
GoogleEarthImageryProvider._logoData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAAnCAYAAACmP2LfAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAHdElNRQfcDB4TJDr1mp5kAAAAGnRFWHRTb2Z0d2FyZQBQYWludC5ORVQgdjMuNS4xMDD0cqEAAB1zSURBVHhe7ZwHeFTFFsf/u+l9N70npOxuSAKEFFIhCSH0qhEQUHkgKCgWUFGBB6IoCAoo0ntooaRvEkIIBBBpoYSa3nvvfd+5u4sQUigPfMX8v2/Y3Tkzs3fv/d0z58zcgF69Ql1SY+MM1wQJem44ZeiJk8beEOqPwG6uC7ZqyElb9eo/JZEIkH2nRQkBIlNMauuPCS3uGN/kjkmNDghoskBAgzrZ2NLmf1+JwIKQpYsoxdmIV9+N07onCegzBPM9bOdmYKnazF6g/1N6UySPqSJzvCaaiLHtP8G/Phq+FRfgU5ogKWUXMLT6Mvzqr2BE40mMadqO8c3zMabBC6PqDDC8SlY60t9HByCLVTKu+ERmHr5TWI9wjVxEaOZivWo1pil8D1tZeWnLXv1l8iZ3PF2kjymiWRgvCoJv5U243IyAXcQq8A9Mg9W+4bDe6wv+kVGwCZkL+4Sf4ZR+BZ5VGQR3EkbWn8Hopm3wq54Lz2JD6ah/P21XGopQ9Qoc16jGSqVyTJWbQbUsibFXf42mihTwZpsvAtp3k0dOhFOSEH1+ngaDefrgjFCgFkxY8fCisCBvKgODzxRh9qslBFGfYmDGLbiV5mBwRRo8KtPhVBgPu8teMP7u73chD6kMRYRGBY5xqrFKqQwz5SdTbS/Qf5mmUYw8rf01CjHC4VP7AHZxO6E3qy9ZZCQNnio2rE/4o9/tkxiQUYp+KRXgx8XC5FsXcLz/hkCrDUU4pxLHuDVYpdwL9F+qqSJZKlPwenskfOoI5tN7YPCJGVme7wKYr5EBXzgYfW+mwTI0Gjrznaj2WW+I/y8dVPdDGLcKRzXrsEqlHO8oTKHaXqAZWe9hQXCi63NhHWYI3ilfWIW/YLjqL2JRiOFBJRz+LffhcPs09D+0J8vzn3zXdBnYnp8Mi6NboTWzH9X8fVc+DhDQodxqAroe36lU9AJNWr4cEAjNwI8OAC9cT1rbUfzwGeCfKiL7dGnNc+q1NiO80b4BY1oT4V6WDcsdc6j2xbyq4wMWrA9rQmeWFn36ey/jBaoPQ4hmLYI0G/AtAf22fC/QDols8ITrIYi/Bl6knbS2o3gRbxHQxQQ0k0S/gCa2v4OJovPwacqAQ1ICjL40klr+UrWoQbFBETo18jCpZsOoFODkvuCNJYoHW3QKXFEM7ETRcKfiQe8d6NVIFImXvg4skhY40mxnQYVRIIeA1qrHEc1GrFSpxFtP99AiFbDbNKDZpAzzGkVYVcvBuBJQEo/9/6C+dyjPitwLwak74D8V6Bfw0P5VShjXFoTR7TfhUZkL29M/wfATJan1lauWC3aDOgyaVDCuTgbf1bFkfmtkye1ogsK2asivLYfCglIoD8qCknI2NHuG4QSVGMgQyMbt0fioRYh9VYcRU7QX55uDcaHtFOJEsThMtmWtQgxsDodsWaC0c3ea3MzGBJEqxrfbYmzr6xjfPAeTmt5HQPO7eK1xDibUz8eY+k8xtHYJPCtXwvHOu7AXMrMTsF/TH8HajTis1YwVqpWY0TXQDKy1OpBr5EJA52Fukxx+bmKxtjWx2DuaWawNlZD5qhzyo9KhpHAbKpJO/6t65UCPbPHA2PYrGNacgkElabCJJDev/MpDhUKKnuq44LRoYEK1IiswkS1zYCfk5y+F0qjvoTwqBOof34dGeAnUL1ZCLboEnJ9zoe0QD/Nuj00UBVXRabzVLETM3S0ICfwA8yc7Y6C3ANYbZsA7aQ1W1xzEfZEQ6dT2BkG9pP4ouo7jGE1u42JS20QMrzkCr4xwuN4+AM+cYII3EaNar2J86zmMrP8DHulCON4NhU3YWuhOYy6SZENpH9cfx7WacFC7BSvUqjBDsRPQIiugURvazeqYVaqAw6dYrJ9WQy7gayj4nYDy3HtQOVQGpYRqKEWXQf2HdGha/AFdae9Xr4czz0ubISRA75ECbSut7agegO75OLxpahze8j5GtifBpzEDLiV30Dd2mNT6StWiCbVmLt5rUkBQCEt2zWzIMSA8HgrIBkLD+Sp0jhHISYXQ/KMYukfvQ3fQxq68XCTBHId/tMTg7LV1CFs4BszJ6hBarBgHlcRv8H7tbuSKQpFPYGe0BmND+nZ0npECaPKf0r4UIxsuoF/IMpitsAVnrA4s15uh3x8fwLXkLobUZGJIXTqcUzbDaJE5FAVq0t4S7dEcjqMEc6B2K5arVWN6Z6AbdOmm5mJelQKOHWSxF44Cy4CqxW0s6RwchCovFRohdGNfLgX3WiZ0N4aD++y7jfwYJUrAPCle/ZjKV+BFTSegrGAZIm3QjXhBytTWB3zhByzryMUU986jz16wD+96ijCNUIAgmkc3tS6G7GERjCbgR82B4OTbEESqIiCIcqsIYzoGGyrBEMSmgh8xBoIIAR2fAHZhj8Z9DOhl9FHeKkSDvn809fuc+iyCddRYaiOZBTvIt1YJfs0b4N+WDO+GHPLQN2Ab7S61vjJV60C9SRPvNSqzTpxlyQfS1dGUmjppK7gW16B/LhN6abnQu5cDwzO3YNhhqqK4WJY887sEdGzWFpxfOxmDpKZOOvgWFB8sx9L6nShvP4FyUQjKGg5gScpGKEqbUE7RxiGYv6QQ4zIG/r4D2m88sjEy/EIW/a6+TQ4gHe5VhXCvy4JL7gLYnesI2i6t4Tii04r92u1YKt767gB0ozrkGzmY26zEOh7Hkt+kAKhLTX9qOVVdg9aoNOjcToR+wUVKLYKgN0Zq7l7884wn9CKgr4AfWw/B6SwqKQRKOdXVghe9CpbherASSjtIpGpxRIHFjwygNreoXy0lb+lU7lHJBP9kPcGXQnBNghUB/Lh44fbUp5JA+5Hs71LbPPLCVRDEJZDNGIJgeQI6mG6KegKzldq1U7tGKjQmHR8vwl86kgRoAQN0xBw6ztn0nQ/ocxEdQ7L4d/BjG6g+m8aZTL/xsXPuW82Fb8t+DG1Ox5D6XAwqvQ67OA+p9ZWoUQPsei78mjSwNU9GLmEzVGZJTd3qFPTn3YZhXgYMMjNhlHsDxms/hNWfoUdrNPgEc2h7BG5d/Bo7Blt0BuNxXf4MVmXrkdRyEHWiY6hr2oc7mevRX2wc18gioEeI1+N9a+/CNnImVAZ0mhEoNOPAJT8MHjUF8KTiWhqHgbfMpVaJdhLQh3XasU9bJAZ6ekeg6zQwgEKuLSWysmd3QGmatLqD8qDNug3dCX/AIPk4jGr2wDB/JXTmkan70IvmZTY/rB9BdZlKLkG0lG0d5klAObKsw1+jzyFiWPnRawiaDrMYwTyMwMwh220WP2IWFVfqN4CKO8E3n0C6R/ZUej9Y2kUiMdDRFTRePH3nA3q/m7xpAEtAXl0QrkTwscnmS/3eptdzNEYevZLnZ5booqk8tuYs9tAny+n1LL1mghezlcULH0VtHamOZhvhIvoNOXQsd2EZIbluYnlWaMO75TCFG9kYXJ8H14o76H/10Z3yClSrCm6jGtbWK7LC7kIlYRfUmY2XHnUa+mbXYRSfCuNCptyE6b1jMBD/EPKwchQPLxGdxOWWI8iKXYBPqLozgI8pfA5YBWvxbfMeNLUfRmPTLjRnr8YKsdGvRQ5j2zZTSSRQ78H+7GhxfScFAINypsG9ukDspZ0LKKE+O0pqlGi71ggcIqD3dga6RhFKjSqYT+VEFkvu/E9Q+HNWKaE2VVDgVkPFqwAaay5CN3En9M59BM2vfKDs7AvljjPGE5LlharQdL+LoCmhOHU0rIUyD+NgVTOa+q2iVQiIcAKpHtbhXuJOjPqeVCRYThNE6VTvKNs3hM3cHGIxntxKyCbP7Erj1lHZJbVIJAG6iiCroZCAPGukvOyASJbvCgoaAoKoAQ1kHcGC7nmZDkmhBR2PfSQLtkcl4zCSAE2eO6qExYuYxrE4KqdvelBiM4+ncYQy1IY8d0wbhUSLJAZGbsUceNYdwJCGPAyuy4NbZToG3JoO1Qk9AvHvqF4ejo0KCKlisyl04Jw+AE1ma71HRUJP+QqM1t2HcVEyTEoSYVYQCuN3HenCt4XDhGA+KorAnYZ9KIj5ELOl3XpU/k/wrt+OmraDaG7cjpacbxFvYAAZDG5Vw/DWCxjRdp+ATsWAS6+D69H1+XDNsoVb1T06b0VwzCmBIOYdqUWibTojcFBH1CXQctBtUcA6Oh/RmVC4sBmKA5j6erC1qqE4sRpqG25A43QIOHuXgvOmP5R4ZH6m5UY2L9SSLjZ5sKjjsI/o8olH8ngjCZoSgmw9DMIl3t42Up0g+pq89/sEjLK47knZhSkSuDepJP4JOyNJyEFAR8VQKMOR1nbWM69yxNJYwh+VLE90ffPyxLE3EwL9Jq0huWQqwL1iA7zq8+FVl0+epgBO6T+gb2TH+OglqgastxtZrNNlkLt8E5oJx6HZdab7mFZBk3UZRjMewCT7HkzLfodZxREYr5sBjiIBPYiAPt8ehvSGPSg5vwjzpd16VNkmmDTswp22QDTXbkJrxhJkzHGDFoUQmvBpvo2hrZl0TnLhlLIYfUO7nt7dSg3hURcP1/JiDEgphuXBqVKLRFsfA3oJAf3mI6Cr2OjTwGYdqWGzzmZD6WoYVCfehdqsZKjuuwS1oB1Q+5piHac3oaxBzZ9vLZ4nHEeesoXg6niDPSYWP9yUgD5PHu48eKE64krHcErchHIEuRysTpAXjObQWIYEHiV4EQYEojp5aEoyY+IIpOQugKYYOnIdJXrdJ63PtWwXMQM6m6SVT4gfZkbHV0XHsVtaQ3K8yoJr0YfwoHDDq5ZiQSqDik/B4Q9taYtn18gyNia1qGJsmTrGlUjK2FJ1jCjRwOASDnkxDvN95ZD/og5yl0qgfCMJ2leDoeksHaFHXYOJVyrMkm/DrPwMzGr2wmjnLGipthyHL0W7t9pDkduwF2U3lmGFtvbTdyirt0OreT+iWwPRUrUBbSkLkT/fCUZwKVYikBMwpDlPXNzLwuAQ2rWX8KzUh2dDDJyLSmB7/S5Mf3WRWiR6CPSezkCXQs6qBnLCKsheyoXqnTCoL9oOFd9/Qtl9KJT6UJMX3/zhCz8iuCjhiviSYtMx3ZTJBN8lCE7eIRgF0p6krRRaRBDskTTGySBKws5SuUjJHYUiMQdpzCUE0Q3y5MnSDhJJQg5JUvjSgO5hHZofaioGmvc40IycMgbRtJktjgOZ5Ma9irzSg46xYHcaVEZevkgBHqUWGFK+FENKQ+BdGAq/wiMYWbwHI6h4FwTDOes0BMKFMHxPNg9qn1dANakYanfuQSs5FJoTpaP1qBswsSGgb9+EeUU0Af0LDH4dBhXlmv3wajuOpPYQFDcEojxtNQ6sn9ZzUsiofjfUWg/iYOt+tJatRtvN95DqZgxNuKTKwLV4Jdyqc8Wz1uCGTLjmDIVDQqewQ8anwpJi6GsYkF4Ey2O/QvsfXKlJIgboAwT07s5AZ0G1TylUIsuhdKMI6vcuQ3PVAqg+9UZ8JvGEywiuNoIwD4IzaV2X+HSa1otgE3+NwJImVkycG0kx8snfyUZJW+QFApeSu+hN9BpIn6n+ZBp9bqDv+C8Fum+8IpzzJNOmR3UhTaGFcC07iAHXmamuZw28C/S/aIt+CcthF7+ToN0EQdhqOFzcBu/Sm/ApvAGX3DzYXIiF9jtWTJf74L6ZC83UfGg8SId2xnloSZKxp+gWjC0J6KSrMK8KhmnlSugtInpkCzaBV78Hl5oPoaLpECrLt+Bi4jfgS7t1q+YDUGsPwj5KDFsLlqD97JuIpmpZmP+TftM1ezjlxsOllM4H3eReDWHwKrOBW84jqMeK5OBTv4Bu6HxxgqU1s/N3MkAHSoH+ioCe+gjoJHB0s8ENLID6/UJo3E+GVlwoNEwY278tXhR50RhmeexzgmM8JXjdF36MHwEoiXn70Csv6gxBm8PiRc6gJFD1HDzFpq1cP0omo5QJZAfqQzH0f6uHZjQgeR4cC/IJZCnUtSkYVPAWBiX2/CdU/S7Ql+9TgtFCTaiP0qAEXA2yRsqwuzECziWZcM4tgv2DSljF7ID+l+JNh9+hY38HuvcYmLOhk5EEnVPfQOmpW+33YGaXhj53E2BWuxvGebOh5cPUX/sWSgXrsa9mB2qaDqCK4C7I2IA3jn8u7tat2g6D034MIbWb0fZgHlr2DscXUhNNuYdkYRPrg/7JiXDMLYBrZS6GNEZgVJM/JjWY4I16G4xr/BCDq2nKjjoAvY+Zpwo7eXBskQK9Swr0lEdAn4a2wk3o/DMNWmn54KYUQIuZsebGQuXFQ42H4kfNk4QckSOkNZ1lGkGAUoInOKkAm2jJsVtH+om9Nj9ytZxNcNdhljXByo+JJXj/i4G2u2xM02YInPJLxFB7VudTPH0ZHkWu0hbPpwHpfnAszoFDVgVsb1fDMmoL9L8S7wTFQE/1AvR33oB+QSp0czKgl34B2iO9uwJCKib5SGaZjbqLPlkhMG1YDr1gQyioSs24vQTDitagsnIL6loCUVu9C2EJK9FjYtsWBNP2Q7hb9A155zdwY5mTeGexo0w32hEcy2F7JQaOqZfgk38KY6rDMKFBiGHNt+iGPgCNYd0/s/sbAb2fgN5JQC9Wq8bkR0AzioOOx3Xo30mGbnY+tNMKoJOQCm03qfnFxRf6E1yUFAqZJcyuZRWuQmB+TWHJcgJfkjPxImcSSIUsXviMx/O9DvqfALrPDjb6nhuBAWkZ5JFKKTYuIqhz4FUdAo9CGwzO7Ra2LjUg0w9OxdlwyKxAXzHQm8lDi4HeAT1WMPSHnYXR7aswKE6Gfl4K9PdfgZ6+uG8XSmMbKyXD/LsEmFduglH2NHA7rA3Hvg+Ve1/gYO4KNFRvQUPLQVRU7MG4yn1dJ4eiULAo3JhW9xsa77+Hml8GY8FQ425uAM5wRRivNoPlTjs4XhoH35oLGFZ/S/wglyDkbWmrrsUAvY+A3kHlSwJ6ihKzCvLnuQyElmIs9LdfhmHxA+jn5kI3jcrRFOjxU6DTbTx9DybsOBh0f034EeYEVyaFD0IYhnQ9y1pTIsiPvU5AnKYkUBL78yKmQhDLgDRPSWtPp/HFkFtHqFCfRBr73wX67qsD+qFsEubCnqKBAZllcCkkT12RjSHVMfApH0bJXfcH+aQGZg6FU1EWeeoK2NwgoMM3Q++zP/fq/Smf2g392ZEwzk2Acfl9GBHURmuSYPyn132oHBizH8B8wjX0SadQI2cWtOZZQbHTdEgRn8XN93EiczFayn5GU3Mg7lJMPab5SEeoCWZZ0TF4Ne/A/ZSPUbXdDz9Qdddrrk/KtcwR7jX34VXDzGCFGFT0GzyLu922x069kdiv145tOu34jlOHBWoz4arUAZQt0LYOhmFcHJ2H6zAsYnZDc2FwKhv60+m9UQrLUJ4hSYQAVhpM1O6jj30EDD33Q6frZyoY8cMVaWZZR560kuB5V9H6iVUas+Py5L1/IHsT2ZldR4nEkMdkUd8Y8tYd43mLIMhYhenDWvgjQSQiGFOkiEv0rEAzK2u8yG10M2WwBWFdb6q9NKDNd6rCOuYD9L2VI/57QMfcEniU5cCnJgG+lR9haAnz4MzT5ZjmA4e8HBqnGtYXamF+nK7bpx0uwHxoqGyE3sKD5HHjYVJ1C6Z5qTD5Ph2G1hnQEV/0LBhxU2E+4yYsbgTCJGsuNBfYQrnjA0CPxDo2CRYJ0xGesgD1ZWvQ3LQbKeSJ54uC0UcUDVVRGExFR/FB2y7cSf4C+Zv9sXSUeQ9P2z2pQdnmBHQsPKqKqFCyWJsM75o1GMw8O/iEhFZs/KK9CD9wRfhCTYTP1dqwnBOHrQYz8IuuH5ZxxI/MLQZH5kfoeu6D4cVQGNecgXHFbRgXZsD4Xg5MjqfDeE0KTBbRDLXsLiwOR8HkxCJoOs+Eavdr08ZBBGdYP7rYzAZILsH3LYUYtgSsAXlYRwLqW0r8Ksl2id4/Onaz47IE+kayUfwddYhsgwkqXRrLgOpHEuyhVF9B7ytoTAL//qNjeFagGfGEi5nvYPEifqOx/ek4p1J/8aKBWC8N6Icy2+oL6zOhECTmw46SuoHZpXBn/pK7/DK8K1bCp3Q0vAv7wqfIBD55OuS9teFVYASPfAFccseThw+E4Ho5LOMqYB6ZCeOdK6H1bleJH2sOOPZradqlC3otDqY5F2GafQmmCZdgFnMBZteEML2yCnprh0CZWVp66gbDuD5Q2uSLUacm43jSB0gq+h55JeuRX7wRqUUbkJL8DS4GTcPqCdZgduZ6XiZjgvcp9fIY3aAH/yY+3KvcMDBjLSXQBXDML4VbaQG8a9PgUxcOzyIneKY/Or6FHDO8q7INY+RiMFJaJijE4i2VeEylej/FDs99TAPH8Dvofv8bDK/vhVHxMRhX0W+vOgXTijiY5UXANGkNnYeRUGN2VrsPNx6XVaQNgRNM03sBgUjeOKJJ/Cr+LNzFsg61YB5/elyKtic0qM031CaZAG0gqJnVEuYBIoI49gy9D6DXrQR3GoU2j3YE+WE2FI9TGBG1FLywnhNbPt1Y/OhY+o5iGqsGNmdLaVxfqZUB+g0Iztwi2AOkNZ3FCzOm30bHeHK9tKYHKfPZMFhlAtM9c2EpjALv93zY3qlE/8xyOOUVUTiSBrfy83CvDIdbRZC4uJSGwzHzd0qgkmEVfRnGW/dC79vPobtkFLRmm0HDpVt43MnrzoOm/dfQeeOf0P3wB+guJogXrIDuhHfAsdOFbKdQ5GkaYQbNNYNht2c8/AOnYNKB6Ri//Q14zRwIuohdPC76pCbWKGFCkx9GNC7B0NZD8CiJh8Odi7A59zud7EuwvU4hVUYZBhUXwqsqA56V0RiUM1Dam36UoiyFuprQhc6fRZuKKhV5+rcLKD2hrPQ+NPsvgNb0j6C9eCG0v/kU2l9/BK0ZM8EdRJQ833noG8Qib6lDkA0lYD6i8GIJlffZ/IhhbJtQjW4TP164EiWWztTnH9T+a4L/MxpjAn02hWWYDAQnefSZzm7Io7zDOpiSzGh3grwPwd3zDccPZdH4phBEkXcWBrD4wlE07qObw5pmBUGsK43T/YPfgmAFWEe5U2EeCXhGcV5nQ3u2KrTf6w+jdTNhtud7mB/ZC4vg43QAwbAMDYLF0e3os+8HGP80D7oLx0F9dD+oj9AGZ4Y85K0Yj/Vs3kQiFgeybFPIySiDzdwAz9O3JzHjPNtYk8gjv948FOOatlGodR0Dk07Bau9n0F8wFBp+luBO1CXeuDD51Q3830PRP7UIzgUlcC0vhHPRSdic6eI53ecT3W0sKyjI2EFRxhzyz3sOO8voBkEUTclYhAyshCwr642PR79diwlbBOEs8vLMFjgbbuelhpeoz5rEDxsNNl/+9ON5RWJOLsXCysQdh5IhWWbzhUmoel6v/l/RxGpZTKgbh3EtEZQMp5AX2ASd2f3AVu7695ky/7nOuc2U/BZSCFIGp+I82F/rfprsVa/+Mk0sZ2F0tTvGNZ+gRO8B7C/HQ92beWine+/IDWDBbJUmbBN/hUNOGRyyStH34vfQeP3ZV4R61atXIu9Kefg1rIB/XRJciwso9nymLXmxbP+wxcCsVAxIKwfv1AZoDH96jN6rXr1SuVeowKsuFINrs+BSXATbc59JLU/XwCwdDMw7B/vUEpgHfQYZ7v9HCNar/2E55ynDpSwYrhXF4uKUeQiY0/Oy3kM555nCITcJgmvp0F30Yo8L9KpXL1X9E2XhkPoVBuYWwbmolKDOhmv+WHiXyGNkgbTRE1pOublXkRycCz+AfUoRzPdsgKJN1w/19KpXf7n6xlnCPikE/SkWdswrozDkNoZUfIWhFTYYWaPy4a6NkgSR2XAZXSOLIWUWcCv7FP1T7sH8wFZwp7ycxz971auXIm4AG+b77MFLEKLv7ULJMy0FefCsPAOv0t0YUrIMg0s+gVfxYrgVbIJLUSzsrl2F2ZZl4L7J/Pdp/956ca969UrEna0O41/HwSJ4F3in42Fz5Trsbt5Bv3u30e9uImyvnoV15GGY/LIA6kOZP1966pZ8r3r1n5eqhwZ0F/aB4ToHGK9zh/FPHjD60RE6H1tDaaA2cdy7mvFfI+BffksPNrEksu0AAAAASUVORK5CYII=';
function buildImageUrl(imageryProvider, x, y, level) {
var imageUrl = imageryProvider._imageUrlTemplate;
imageUrl = imageUrl.replace('{x}', x);
imageUrl = imageUrl.replace('{y}', y);
// Google Earth starts with a zoom level of 1, not 0
imageUrl = imageUrl.replace('{zoom}', (level + 1));
var proxy = imageryProvider._proxy;
if (defined(proxy)) {
imageUrl = proxy.getURL(imageUrl);
}
return imageUrl;
}
return GoogleEarthImageryProvider;
});
/*global define*/
define('Scene/GridImageryProvider',[
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'../Core/GeographicTilingScheme',
'../ThirdParty/when'
], function(
Color,
defaultValue,
defined,
defineProperties,
Event,
GeographicTilingScheme,
when) {
'use strict';
var defaultColor = new Color(1.0, 1.0, 1.0, 0.4);
var defaultGlowColor = new Color(0.0, 1.0, 0.0, 0.05);
var defaultBackgroundColor = new Color(0.0, 0.5, 0.0, 0.2);
/**
* An {@link ImageryProvider} that draws a wireframe grid on every tile with controllable background and glow.
* May be useful for custom rendering effects or debugging terrain.
*
* @alias GridImageryProvider
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme for which to draw tiles.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
* parameter is specified, the WGS84 ellipsoid is used.
* @param {Number} [options.cells=8] The number of grids cells.
* @param {Color} [options.color=Color(1.0, 1.0, 1.0, 0.4)] The color to draw grid lines.
* @param {Color} [options.glowColor=Color(0.0, 1.0, 0.0, 0.05)] The color to draw glow for grid lines.
* @param {Number} [options.glowWidth=6] The width of lines used for rendering the line glow effect.
* @param {Color} [backgroundColor=Color(0.0, 0.5, 0.0, 0.2)] Background fill color.
* @param {Number} [options.tileWidth=256] The width of the tile for level-of-detail selection purposes.
* @param {Number} [options.tileHeight=256] The height of the tile for level-of-detail selection purposes.
* @param {Number} [options.canvasSize=256] The size of the canvas used for rendering.
*/
function GridImageryProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
this._cells = defaultValue(options.cells, 8);
this._color = defaultValue(options.color, defaultColor);
this._glowColor = defaultValue(options.glowColor, defaultGlowColor);
this._glowWidth = defaultValue(options.glowWidth, 6);
this._backgroundColor = defaultValue(options.backgroundColor, defaultBackgroundColor);
this._errorEvent = new Event();
this._tileWidth = defaultValue(options.tileWidth, 256);
this._tileHeight = defaultValue(options.tileHeight, 256);
// A little larger than tile size so lines are sharper
// Note: can't be too much difference otherwise texture blowout
this._canvasSize = defaultValue(options.canvasSize, 256);
// We only need a single canvas since all tiles will be the same
this._canvas = this._createGridCanvas();
this._readyPromise = when.resolve(true);
}
defineProperties(GridImageryProvider.prototype, {
/**
* Gets the proxy used by this provider.
* @memberof GridImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return undefined;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight : {
get : function() {
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
return undefined;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
return undefined;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
return this._tilingScheme.rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
return undefined;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof GridImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof GridImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return true;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof GridImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link GridImageryProvider#ready} returns true.
* @memberof GridImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return undefined;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof GridImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return true;
}
}
});
/**
* Draws a grid of lines into a canvas.
*/
GridImageryProvider.prototype._drawGrid = function(context) {
var minPixel = 0;
var maxPixel = this._canvasSize;
for (var x = 0; x <= this._cells; ++x) {
var nx = x / this._cells;
var val = 1 + nx * (maxPixel - 1);
context.moveTo(val, minPixel);
context.lineTo(val, maxPixel);
context.moveTo(minPixel, val);
context.lineTo(maxPixel, val);
}
context.stroke();
};
/**
* Render a grid into a canvas with background and glow
*/
GridImageryProvider.prototype._createGridCanvas = function() {
var canvas = document.createElement('canvas');
canvas.width = this._canvasSize;
canvas.height = this._canvasSize;
var minPixel = 0;
var maxPixel = this._canvasSize;
var context = canvas.getContext('2d');
// Fill the background
var cssBackgroundColor = this._backgroundColor.toCssColorString();
context.fillStyle = cssBackgroundColor;
context.fillRect(minPixel, minPixel, maxPixel, maxPixel);
// Glow for grid lines
var cssGlowColor = this._glowColor.toCssColorString();
context.strokeStyle = cssGlowColor;
// Wide
context.lineWidth = this._glowWidth;
context.strokeRect(minPixel, minPixel, maxPixel, maxPixel);
this._drawGrid(context);
// Narrow
context.lineWidth = this._glowWidth * 0.5;
context.strokeRect(minPixel, minPixel, maxPixel, maxPixel);
this._drawGrid(context);
// Grid lines
var cssColor = this._color.toCssColorString();
// Border
context.strokeStyle = cssColor;
context.lineWidth = 2;
context.strokeRect(minPixel, minPixel, maxPixel, maxPixel);
// Inner
context.lineWidth = 1;
this._drawGrid(context);
return canvas;
};
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
GridImageryProvider.prototype.getTileCredits = function(x, y, level) {
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link GridImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*/
GridImageryProvider.prototype.requestImage = function(x, y, level) {
return this._canvas;
};
/**
* Picking features is not currently supported by this imagery provider, so this function simply returns
* undefined.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
GridImageryProvider.prototype.pickFeatures = function() {
return undefined;
};
return GridImageryProvider;
});
/*global define*/
define('Scene/MapboxImageryProvider',[
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/MapboxApi',
'./UrlTemplateImageryProvider'
], function(
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
MapboxApi,
UrlTemplateImageryProvider) {
'use strict';
var trailingSlashRegex = /\/$/;
var defaultCredit1 = new Credit('© Mapbox © OpenStreetMap', undefined, 'https://www.mapbox.com/about/maps/');
var defaultCredit2 = [new Credit('Improve this map', undefined, 'https://www.mapbox.com/map-feedback/')];
/**
* Provides tiled imagery hosted by Mapbox.
*
* @alias MapboxImageryProvider
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {String} [options.url='https://api.mapbox.com/v4/'] The Mapbox server url.
* @param {String} options.mapId The Mapbox Map ID.
* @param {String} [options.accessToken] The public access token for the imagery.
* @param {String} [options.format='png'] The format of the image request.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
* this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
* to result in rendering problems.
* @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
*
*
* @example
* // Mapbox tile provider
* var mapbox = new Cesium.MapboxImageryProvider({
* mapId: 'mapbox.streets',
* accessToken: 'thisIsMyAccessToken'
* });
*
* @see {@link https://www.mapbox.com/developers/api/maps/#tiles}
* @see {@link https://www.mapbox.com/developers/api/#access-tokens}
*/
function MapboxImageryProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var mapId = options.mapId;
if (!defined(mapId)) {
throw new DeveloperError('options.mapId is required.');
}
var url = defaultValue(options.url, 'https://api.mapbox.com/v4/');
this._url = url;
this._mapId = mapId;
this._accessToken = MapboxApi.getAccessToken(options.accessToken);
this._accessTokenErrorCredit = MapboxApi.getErrorCredit(options.key);
var format = defaultValue(options.format, 'png');
if (!/\./.test(format)) {
format = '.' + format;
}
this._format = format;
var templateUrl = url;
if (!trailingSlashRegex.test(url)) {
templateUrl += '/';
}
templateUrl += mapId + '/{z}/{x}/{y}' + this._format;
if (defined(this._accessToken)) {
templateUrl += '?access_token=' + this._accessToken;
}
if (defined(options.credit)) {
var credit = options.credit;
if (typeof credit === 'string') {
credit = new Credit(credit);
}
defaultCredit1 = credit;
defaultCredit2.length = 0;
}
this._imageryProvider = new UrlTemplateImageryProvider({
url: templateUrl,
proxy: options.proxy,
credit: defaultCredit1,
ellipsoid: options.ellipsoid,
minimumLevel: options.minimumLevel,
maximumLevel: options.maximumLevel,
rectangle: options.rectangle
});
}
defineProperties(MapboxImageryProvider.prototype, {
/**
* Gets the URL of the Mapbox server.
* @memberof MapboxImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof MapboxImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._imageryProvider.ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof MapboxImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._imageryProvider.readyPromise;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by the instance. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle: {
get : function() {
return this._imageryProvider.rectangle;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
return this._imageryProvider.tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight : {
get : function() {
return this._imageryProvider.tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
return this._imageryProvider.maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true. Generally,
* a minimum level should only be used when the rectangle of the imagery is small
* enough that the number of tiles at the minimum level is small. An imagery
* provider with more than a few tiles at the minimum level will lead to
* rendering problems.
* @memberof MapboxImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
return this._imageryProvider.minimumLevel;
}
},
/**
* Gets the tiling scheme used by the provider. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
return this._imageryProvider.tilingScheme;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
return this._imageryProvider.tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error.. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof MapboxImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._imageryProvider.errorEvent;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
* @memberof MapboxImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._imageryProvider.credit;
}
},
/**
* Gets the proxy used by this provider.
* @memberof MapboxImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._imageryProvider.proxy;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof MapboxImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return this._imageryProvider.hasAlphaChannel;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
MapboxImageryProvider.prototype.getTileCredits = function(x, y, level) {
var credits = defaultCredit2.slice();
if (defined(this._accessTokenErrorCredit)) {
credits.push(this._accessTokenErrorCredit);
}
return credits;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link MapboxImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
MapboxImageryProvider.prototype.requestImage = function(x, y, level) {
return this._imageryProvider.requestImage(x, y, level);
};
/**
* Asynchronously determines what features, if any, are located at a given longitude and latitude within
* a tile. This function should not be called before {@link MapboxImageryProvider#ready} returns true.
* This function is optional, so it may not exist on all ImageryProviders.
*
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*
* @exception {DeveloperError} pickFeatures
must not be called before the imagery provider is ready.
*/
MapboxImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
return this._imageryProvider.pickFeatures(x, y, level, longitude, latitude);
};
return MapboxImageryProvider;
});
/*global define*/
define('Scene/Moon',[
'../Core/buildModuleUrl',
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/Ellipsoid',
'../Core/IauOrientationAxes',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/Simon1994PlanetaryPositions',
'../Core/Transforms',
'./EllipsoidPrimitive',
'./Material'
], function(
buildModuleUrl,
Cartesian3,
defaultValue,
defined,
defineProperties,
destroyObject,
Ellipsoid,
IauOrientationAxes,
Matrix3,
Matrix4,
Simon1994PlanetaryPositions,
Transforms,
EllipsoidPrimitive,
Material) {
'use strict';
/**
* Draws the Moon in 3D.
* @alias Moon
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.show=true] Determines whether the moon will be rendered.
* @param {String} [options.textureUrl=buildModuleUrl('Assets/Textures/moonSmall.jpg')] The moon texture.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.MOON] The moon ellipsoid.
* @param {Boolean} [options.onlySunLighting=true] Use the sun as the only light source.
*
*
* @example
* scene.moon = new Cesium.Moon();
*
* @see Scene#moon
*/
function Moon(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var url = options.textureUrl;
if (!defined(url)) {
url = buildModuleUrl('Assets/Textures/moonSmall.jpg');
}
/**
* Determines if the moon will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* The moon texture.
* @type {String}
* @default buildModuleUrl('Assets/Textures/moonSmall.jpg')
*/
this.textureUrl = url;
this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.MOON);
/**
* Use the sun as the only light source.
* @type {Boolean}
* @default true
*/
this.onlySunLighting = defaultValue(options.onlySunLighting, true);
this._ellipsoidPrimitive = new EllipsoidPrimitive({
radii : this.ellipsoid.radii,
material : Material.fromType(Material.ImageType),
depthTestEnabled : false,
_owner : this
});
this._ellipsoidPrimitive.material.translucent = false;
this._axes = new IauOrientationAxes();
}
defineProperties(Moon.prototype, {
/**
* Get the ellipsoid that defines the shape of the moon.
*
* @memberof Moon.prototype
*
* @type {Ellipsoid}
* @readonly
*
* @default {@link Ellipsoid.MOON}
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
}
});
var icrfToFixed = new Matrix3();
var rotationScratch = new Matrix3();
var translationScratch = new Cartesian3();
var scratchCommandList = [];
/**
* @private
*/
Moon.prototype.update = function(frameState) {
if (!this.show) {
return;
}
var ellipsoidPrimitive = this._ellipsoidPrimitive;
ellipsoidPrimitive.material.uniforms.image = this.textureUrl;
ellipsoidPrimitive.onlySunLighting = this.onlySunLighting;
var date = frameState.time;
if (!defined(Transforms.computeIcrfToFixedMatrix(date, icrfToFixed))) {
Transforms.computeTemeToPseudoFixedMatrix(date, icrfToFixed);
}
var rotation = this._axes.evaluate(date, rotationScratch);
Matrix3.transpose(rotation, rotation);
Matrix3.multiply(icrfToFixed, rotation, rotation);
var translation = Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame(date, translationScratch);
Matrix3.multiplyByVector(icrfToFixed, translation, translation);
Matrix4.fromRotationTranslation(rotation, translation, ellipsoidPrimitive.modelMatrix);
var savedCommandList = frameState.commandList;
frameState.commandList = scratchCommandList;
scratchCommandList.length = 0;
ellipsoidPrimitive.update(frameState);
frameState.commandList = savedCommandList;
return (scratchCommandList.length === 1) ? scratchCommandList[0] : undefined;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see Moon#destroy
*/
Moon.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* moon = moon && moon.destroy();
*
* @see Moon#isDestroyed
*/
Moon.prototype.destroy = function() {
this._ellipsoidPrimitive = this._ellipsoidPrimitive && this._ellipsoidPrimitive.destroy();
return destroyObject(this);
};
return Moon;
});
/*global define*/
define('Scene/NeverTileDiscardPolicy',[], function() {
'use strict';
/**
* A {@link TileDiscardPolicy} specifying that tile images should never be discard.
*
* @alias NeverTileDiscardPolicy
* @constructor
*
* @see DiscardMissingTileImagePolicy
*/
function NeverTileDiscardPolicy(options) {
}
/**
* Determines if the discard policy is ready to process images.
* @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
*/
NeverTileDiscardPolicy.prototype.isReady = function() {
return true;
};
/**
* Given a tile image, decide whether to discard that image.
*
* @param {Image} image An image to test.
* @returns {Boolean} True if the image should be discarded; otherwise, false.
*/
NeverTileDiscardPolicy.prototype.shouldDiscardImage = function(image) {
return false;
};
return NeverTileDiscardPolicy;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/AdjustTranslucentFS',[],function() {
'use strict';
return "#ifdef MRT\n\
#extension GL_EXT_draw_buffers : enable\n\
#endif\n\
uniform vec4 u_bgColor;\n\
uniform sampler2D u_depthTexture;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
if (texture2D(u_depthTexture, v_textureCoordinates).r < 1.0)\n\
{\n\
#ifdef MRT\n\
gl_FragData[0] = u_bgColor;\n\
gl_FragData[1] = vec4(u_bgColor.a);\n\
#else\n\
gl_FragColor = u_bgColor;\n\
#endif\n\
return;\n\
}\n\
discard;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/CompositeOITFS',[],function() {
'use strict';
return "uniform sampler2D u_opaque;\n\
uniform sampler2D u_accumulation;\n\
uniform sampler2D u_revealage;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
vec4 opaque = texture2D(u_opaque, v_textureCoordinates);\n\
vec4 accum = texture2D(u_accumulation, v_textureCoordinates);\n\
float r = texture2D(u_revealage, v_textureCoordinates).r;\n\
#ifdef MRT\n\
vec4 transparent = vec4(accum.rgb / clamp(r, 1e-4, 5e4), accum.a);\n\
#else\n\
vec4 transparent = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);\n\
#endif\n\
gl_FragColor = (1.0 - transparent.a) * transparent + transparent.a * opaque;\n\
}\n\
";
});
/*global define*/
define('Scene/OIT',[
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/PixelFormat',
'../Core/WebGLConstants',
'../Renderer/ClearCommand',
'../Renderer/DrawCommand',
'../Renderer/Framebuffer',
'../Renderer/PixelDatatype',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'../Renderer/Texture',
'../Shaders/AdjustTranslucentFS',
'../Shaders/CompositeOITFS',
'./BlendEquation',
'./BlendFunction'
], function(
BoundingRectangle,
Color,
defined,
destroyObject,
PixelFormat,
WebGLConstants,
ClearCommand,
DrawCommand,
Framebuffer,
PixelDatatype,
RenderState,
ShaderProgram,
ShaderSource,
Texture,
AdjustTranslucentFS,
CompositeOITFS,
BlendEquation,
BlendFunction) {
'use strict';
/**
* @private
*/
function OIT(context) {
// We support multipass for the Chrome D3D9 backend and ES 2.0 on mobile.
this._translucentMultipassSupport = false;
this._translucentMRTSupport = false;
var extensionsSupported = context.floatingPointTexture && context.depthTexture;
this._translucentMRTSupport = context.drawBuffers && extensionsSupported;
this._translucentMultipassSupport = !this._translucentMRTSupport && extensionsSupported;
this._opaqueFBO = undefined;
this._opaqueTexture = undefined;
this._depthStencilTexture = undefined;
this._accumulationTexture = undefined;
this._translucentFBO = undefined;
this._alphaFBO = undefined;
this._adjustTranslucentFBO = undefined;
this._adjustAlphaFBO = undefined;
this._opaqueClearCommand = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0),
owner : this
});
this._translucentMRTClearCommand = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 1.0),
owner : this
});
this._translucentMultipassClearCommand = new ClearCommand({
color : new Color(0.0, 0.0, 0.0, 0.0),
owner : this
});
this._alphaClearCommand = new ClearCommand({
color : new Color(1.0, 1.0, 1.0, 1.0),
owner : this
});
this._translucentRenderStateCache = {};
this._alphaRenderStateCache = {};
this._translucentShaderCache = {};
this._alphaShaderCache = {};
this._compositeCommand = undefined;
this._adjustTranslucentCommand = undefined;
this._adjustAlphaCommand = undefined;
this._viewport = new BoundingRectangle();
this._rs = undefined;
}
function destroyTextures(oit) {
oit._accumulationTexture = oit._accumulationTexture && !oit._accumulationTexture.isDestroyed() && oit._accumulationTexture.destroy();
oit._revealageTexture = oit._revealageTexture && !oit._revealageTexture.isDestroyed() && oit._revealageTexture.destroy();
}
function destroyFramebuffers(oit) {
oit._translucentFBO = oit._translucentFBO && !oit._translucentFBO.isDestroyed() && oit._translucentFBO.destroy();
oit._alphaFBO = oit._alphaFBO && !oit._alphaFBO.isDestroyed() && oit._alphaFBO.destroy();
oit._adjustTranslucentFBO = oit._adjustTranslucentFBO && !oit._adjustTranslucentFBO.isDestroyed() && oit._adjustTranslucentFBO.destroy();
oit._adjustAlphaFBO = oit._adjustAlphaFBO && !oit._adjustAlphaFBO.isDestroyed() && oit._adjustAlphaFBO.destroy();
}
function destroyResources(oit) {
destroyTextures(oit);
destroyFramebuffers(oit);
}
function updateTextures(oit, context, width, height) {
destroyTextures(oit);
// Use zeroed arraybuffer instead of null to initialize texture
// to workaround Firefox 50. https://github.com/AnalyticalGraphicsInc/cesium/pull/4762
var source = new Float32Array(width * height * 4);
oit._accumulationTexture = new Texture({
context : context,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.FLOAT,
source : {
arrayBufferView : source,
width : width,
height : height
}
});
oit._revealageTexture = new Texture({
context : context,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.FLOAT,
source : {
arrayBufferView : source,
width : width,
height : height
}
});
}
function updateFramebuffers(oit, context) {
destroyFramebuffers(oit);
var completeFBO = WebGLConstants.FRAMEBUFFER_COMPLETE;
var supported = true;
// if MRT is supported, attempt to make an FBO with multiple color attachments
if (oit._translucentMRTSupport) {
oit._translucentFBO = new Framebuffer({
context : context,
colorTextures : [oit._accumulationTexture, oit._revealageTexture],
depthStencilTexture : oit._depthStencilTexture,
destroyAttachments : false
});
oit._adjustTranslucentFBO = new Framebuffer({
context : context,
colorTextures : [oit._accumulationTexture, oit._revealageTexture],
destroyAttachments : false
});
if (oit._translucentFBO.status !== completeFBO || oit._adjustTranslucentFBO.status !== completeFBO) {
destroyFramebuffers(oit);
oit._translucentMRTSupport = false;
}
}
// either MRT isn't supported or FBO creation failed, attempt multipass
if (!oit._translucentMRTSupport) {
oit._translucentFBO = new Framebuffer({
context : context,
colorTextures : [oit._accumulationTexture],
depthStencilTexture : oit._depthStencilTexture,
destroyAttachments : false
});
oit._alphaFBO = new Framebuffer({
context : context,
colorTextures : [oit._revealageTexture],
depthStencilTexture : oit._depthStencilTexture,
destroyAttachments : false
});
oit._adjustTranslucentFBO = new Framebuffer({
context : context,
colorTextures : [oit._accumulationTexture],
destroyAttachments : false
});
oit._adjustAlphaFBO = new Framebuffer({
context : context,
colorTextures : [oit._revealageTexture],
destroyAttachments : false
});
var translucentComplete = oit._translucentFBO.status === completeFBO;
var alphaComplete = oit._alphaFBO.status === completeFBO;
var adjustTranslucentComplete = oit._adjustTranslucentFBO.status === completeFBO;
var adjustAlphaComplete = oit._adjustAlphaFBO.status === completeFBO;
if (!translucentComplete || !alphaComplete || !adjustTranslucentComplete || !adjustAlphaComplete) {
destroyResources(oit);
oit._translucentMultipassSupport = false;
supported = false;
}
}
return supported;
}
OIT.prototype.update = function(context, framebuffer) {
if (!this.isSupported()) {
return;
}
this._opaqueFBO = framebuffer;
this._opaqueTexture = framebuffer.getColorTexture(0);
this._depthStencilTexture = framebuffer.depthStencilTexture;
var width = this._opaqueTexture.width;
var height = this._opaqueTexture.height;
var accumulationTexture = this._accumulationTexture;
var textureChanged = !defined(accumulationTexture) || accumulationTexture.width !== width || accumulationTexture.height !== height;
if (textureChanged) {
updateTextures(this, context, width, height);
}
if (!defined(this._translucentFBO) || textureChanged) {
if (!updateFramebuffers(this, context)) {
// framebuffer creation failed
return;
}
}
var that = this;
var fs;
var uniformMap;
if (!defined(this._compositeCommand)) {
fs = new ShaderSource({
sources : [CompositeOITFS]
});
if (this._translucentMRTSupport) {
fs.defines.push('MRT');
}
uniformMap = {
u_opaque : function() {
return that._opaqueTexture;
},
u_accumulation : function() {
return that._accumulationTexture;
},
u_revealage : function() {
return that._revealageTexture;
}
};
this._compositeCommand = context.createViewportQuadCommand(fs, {
uniformMap : uniformMap,
owner : this
});
}
if (!defined(this._adjustTranslucentCommand)) {
if (this._translucentMRTSupport) {
fs = new ShaderSource({
defines : ['MRT'],
sources : [AdjustTranslucentFS]
});
uniformMap = {
u_bgColor : function() {
return that._translucentMRTClearCommand.color;
},
u_depthTexture : function() {
return that._depthStencilTexture;
}
};
this._adjustTranslucentCommand = context.createViewportQuadCommand(fs, {
uniformMap : uniformMap,
owner : this
});
} else if (this._translucentMultipassSupport) {
fs = new ShaderSource({
sources : [AdjustTranslucentFS]
});
uniformMap = {
u_bgColor : function() {
return that._translucentMultipassClearCommand.color;
},
u_depthTexture : function() {
return that._depthStencilTexture;
}
};
this._adjustTranslucentCommand = context.createViewportQuadCommand(fs, {
uniformMap : uniformMap,
owner : this
});
uniformMap = {
u_bgColor : function() {
return that._alphaClearCommand.color;
},
u_depthTexture : function() {
return that._depthStencilTexture;
}
};
this._adjustAlphaCommand = context.createViewportQuadCommand(fs, {
uniformMap : uniformMap,
owner : this
});
}
}
this._viewport.width = width;
this._viewport.height = height;
if (!defined(this._rs) || !BoundingRectangle.equals(this._viewport, this._rs.viewport)) {
this._rs = RenderState.fromCache({
viewport : this._viewport
});
}
if (defined(this._compositeCommand)) {
this._compositeCommand.renderState = this._rs;
}
if (this._adjustTranslucentCommand) {
this._adjustTranslucentCommand.renderState = this._rs;
}
if (defined(this._adjustAlphaCommand)) {
this._adjustAlphaCommand.renderState = this._rs;
}
};
var translucentMRTBlend = {
enabled : true,
color : new Color(0.0, 0.0, 0.0, 0.0),
equationRgb : BlendEquation.ADD,
equationAlpha : BlendEquation.ADD,
functionSourceRgb : BlendFunction.ONE,
functionDestinationRgb : BlendFunction.ONE,
functionSourceAlpha : BlendFunction.ZERO,
functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
};
var translucentColorBlend = {
enabled : true,
color : new Color(0.0, 0.0, 0.0, 0.0),
equationRgb : BlendEquation.ADD,
equationAlpha : BlendEquation.ADD,
functionSourceRgb : BlendFunction.ONE,
functionDestinationRgb : BlendFunction.ONE,
functionSourceAlpha : BlendFunction.ONE,
functionDestinationAlpha : BlendFunction.ONE
};
var translucentAlphaBlend = {
enabled : true,
color : new Color(0.0, 0.0, 0.0, 0.0),
equationRgb : BlendEquation.ADD,
equationAlpha : BlendEquation.ADD,
functionSourceRgb : BlendFunction.ZERO,
functionDestinationRgb : BlendFunction.ONE_MINUS_SOURCE_ALPHA,
functionSourceAlpha : BlendFunction.ZERO,
functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
};
function getTranslucentRenderState(context, translucentBlending, cache, renderState) {
var translucentState = cache[renderState.id];
if (!defined(translucentState)) {
var rs = RenderState.getState(renderState);
rs.depthMask = false;
rs.blending = translucentBlending;
translucentState = RenderState.fromCache(rs);
cache[renderState.id] = translucentState;
}
return translucentState;
}
function getTranslucentMRTRenderState(oit, context, renderState) {
return getTranslucentRenderState(context, translucentMRTBlend, oit._translucentRenderStateCache, renderState);
}
function getTranslucentColorRenderState(oit, context, renderState) {
return getTranslucentRenderState(context, translucentColorBlend, oit._translucentRenderStateCache, renderState);
}
function getTranslucentAlphaRenderState(oit, context, renderState) {
return getTranslucentRenderState(context, translucentAlphaBlend, oit._alphaRenderStateCache, renderState);
}
var mrtShaderSource =
' vec3 Ci = czm_gl_FragColor.rgb * czm_gl_FragColor.a;\n' +
' float ai = czm_gl_FragColor.a;\n' +
' float wzi = czm_alphaWeight(ai);\n' +
' gl_FragData[0] = vec4(Ci * wzi, ai);\n' +
' gl_FragData[1] = vec4(ai * wzi);\n';
var colorShaderSource =
' vec3 Ci = czm_gl_FragColor.rgb * czm_gl_FragColor.a;\n' +
' float ai = czm_gl_FragColor.a;\n' +
' float wzi = czm_alphaWeight(ai);\n' +
' gl_FragColor = vec4(Ci, ai) * wzi;\n';
var alphaShaderSource =
' float ai = czm_gl_FragColor.a;\n' +
' gl_FragColor = vec4(ai);\n';
function getTranslucentShaderProgram(context, shaderProgram, cache, source) {
var id = shaderProgram.id;
var shader = cache[id];
if (!defined(shader)) {
var attributeLocations = shaderProgram._attributeLocations;
var fs = shaderProgram.fragmentShaderSource.clone();
fs.sources = fs.sources.map(function(source) {
source = ShaderSource.replaceMain(source, 'czm_translucent_main');
source = source.replace(/gl_FragColor/g, 'czm_gl_FragColor');
source = source.replace(/\bdiscard\b/g, 'czm_discard = true');
source = source.replace(/czm_phong/g, 'czm_translucentPhong');
return source;
});
// Discarding the fragment in main is a workaround for ANGLE D3D9
// shader compilation errors.
fs.sources.splice(0, 0,
(source.indexOf('gl_FragData') !== -1 ? '#extension GL_EXT_draw_buffers : enable \n' : '') +
'vec4 czm_gl_FragColor;\n' +
'bool czm_discard = false;\n');
fs.sources.push(
'void main()\n' +
'{\n' +
' czm_translucent_main();\n' +
' if (czm_discard)\n' +
' {\n' +
' discard;\n' +
' }\n' +
source +
'}\n');
shader = ShaderProgram.fromCache({
context : context,
vertexShaderSource : shaderProgram.vertexShaderSource,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
cache[id] = shader;
}
return shader;
}
function getTranslucentMRTShaderProgram(oit, context, shaderProgram) {
return getTranslucentShaderProgram(context, shaderProgram, oit._translucentShaderCache, mrtShaderSource);
}
function getTranslucentColorShaderProgram(oit, context, shaderProgram) {
return getTranslucentShaderProgram(context, shaderProgram, oit._translucentShaderCache, colorShaderSource);
}
function getTranslucentAlphaShaderProgram(oit, context, shaderProgram) {
return getTranslucentShaderProgram(context, shaderProgram, oit._alphaShaderCache, alphaShaderSource);
}
OIT.prototype.createDerivedCommands = function(command, context, result) {
if (!defined(result)) {
result = {};
}
if (this._translucentMRTSupport) {
var translucentShader;
var translucentRenderState;
if (defined(result.translucentCommand)) {
translucentShader = result.translucentCommand.shaderProgram;
translucentRenderState = result.translucentCommand.renderState;
}
result.translucentCommand = DrawCommand.shallowClone(command, result.translucentCommand);
if (!defined(translucentShader) || result.shaderProgramId !== command.shaderProgram.id) {
result.translucentCommand.shaderProgram = getTranslucentMRTShaderProgram(this, context, command.shaderProgram);
result.translucentCommand.renderState = getTranslucentMRTRenderState(this, context, command.renderState);
result.shaderProgramId = command.shaderProgram.id;
} else {
result.translucentCommand.shaderProgram = translucentShader;
result.translucentCommand.renderState = translucentRenderState;
}
} else {
var colorShader;
var colorRenderState;
var alphaShader;
var alphaRenderState;
if (defined(result.translucentCommand)) {
colorShader = result.translucentCommand.shaderProgram;
colorRenderState = result.translucentCommand.renderState;
alphaShader = result.alphaCommand.shaderProgram;
alphaRenderState = result.alphaCommand.renderState;
}
result.translucentCommand = DrawCommand.shallowClone(command, result.translucentCommand);
result.alphaCommand = DrawCommand.shallowClone(command, result.alphaCommand);
if (!defined(colorShader) || result.shaderProgramId !== command.shaderProgram.id) {
result.translucentCommand.shaderProgram = getTranslucentColorShaderProgram(this, context, command.shaderProgram);
result.translucentCommand.renderState = getTranslucentColorRenderState(this, context, command.renderState);
result.alphaCommand.shaderProgram = getTranslucentAlphaShaderProgram(this, context, command.shaderProgram);
result.alphaCommand.renderState = getTranslucentAlphaRenderState(this, context, command.renderState);
result.shaderProgramId = command.shaderProgram.id;
} else {
result.translucentCommand.shaderProgram = colorShader;
result.translucentCommand.renderState = colorRenderState;
result.alphaCommand.shaderProgram = alphaShader;
result.alphaCommand.renderState = alphaRenderState;
}
}
return result;
};
function executeTranslucentCommandsSortedMultipass(oit, scene, executeFunction, passState, commands) {
var command;
var derivedCommand;
var j;
var context = scene.context;
var framebuffer = passState.framebuffer;
var length = commands.length;
var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled;
passState.framebuffer = oit._adjustTranslucentFBO;
oit._adjustTranslucentCommand.execute(context, passState);
passState.framebuffer = oit._adjustAlphaFBO;
oit._adjustAlphaCommand.execute(context, passState);
var debugFramebuffer = oit._opaqueFBO;
passState.framebuffer = oit._translucentFBO;
for (j = 0; j < length; ++j) {
command = commands[j];
derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand;
executeFunction(derivedCommand, scene, context, passState, debugFramebuffer);
}
passState.framebuffer = oit._alphaFBO;
for (j = 0; j < length; ++j) {
command = commands[j];
derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand;
executeFunction(derivedCommand, scene, context, passState, debugFramebuffer);
}
passState.framebuffer = framebuffer;
}
function executeTranslucentCommandsSortedMRT(oit, scene, executeFunction, passState, commands) {
var context = scene.context;
var framebuffer = passState.framebuffer;
var length = commands.length;
var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled;
passState.framebuffer = oit._adjustTranslucentFBO;
oit._adjustTranslucentCommand.execute(context, passState);
var debugFramebuffer = oit._opaqueFBO;
passState.framebuffer = oit._translucentFBO;
for (var j = 0; j < length; ++j) {
var command = commands[j];
var derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand;
executeFunction(derivedCommand, scene, context, passState, debugFramebuffer);
}
passState.framebuffer = framebuffer;
}
OIT.prototype.executeCommands = function(scene, executeFunction, passState, commands) {
if (this._translucentMRTSupport) {
executeTranslucentCommandsSortedMRT(this, scene, executeFunction, passState, commands);
return;
}
executeTranslucentCommandsSortedMultipass(this, scene, executeFunction, passState, commands);
};
OIT.prototype.execute = function(context, passState) {
this._compositeCommand.execute(context, passState);
};
OIT.prototype.clear = function(context, passState, clearColor) {
var framebuffer = passState.framebuffer;
passState.framebuffer = this._opaqueFBO;
Color.clone(clearColor, this._opaqueClearCommand.color);
this._opaqueClearCommand.execute(context, passState);
passState.framebuffer = this._translucentFBO;
var translucentClearCommand = this._translucentMRTSupport ? this._translucentMRTClearCommand : this._translucentMultipassClearCommand;
translucentClearCommand.execute(context, passState);
if (this._translucentMultipassSupport) {
passState.framebuffer = this._alphaFBO;
this._alphaClearCommand.execute(context, passState);
}
passState.framebuffer = framebuffer;
};
OIT.prototype.isSupported = function() {
return this._translucentMRTSupport || this._translucentMultipassSupport;
};
OIT.prototype.isDestroyed = function() {
return false;
};
OIT.prototype.destroy = function() {
destroyResources(this);
if (defined(this._compositeCommand)) {
this._compositeCommand.shaderProgram = this._compositeCommand.shaderProgram && this._compositeCommand.shaderProgram.destroy();
}
if (defined(this._adjustTranslucentCommand)) {
this._adjustTranslucentCommand.shaderProgram = this._adjustTranslucentCommand.shaderProgram && this._adjustTranslucentCommand.shaderProgram.destroy();
}
if (defined(this._adjustAlphaCommand)) {
this._adjustAlphaCommand.shaderProgram = this._adjustAlphaCommand.shaderProgram && this._adjustAlphaCommand.shaderProgram.destroy();
}
var name;
var cache = this._translucentShaderCache;
for (name in cache) {
if (cache.hasOwnProperty(name) && defined(cache[name])) {
cache[name].destroy();
}
}
this._translucentShaderCache = {};
cache = this._alphaShaderCache;
for (name in cache) {
if (cache.hasOwnProperty(name) && defined(cache[name])) {
cache[name].destroy();
}
}
this._alphaShaderCache = {};
return destroyObject(this);
};
return OIT;
});
/*global define*/
define('Scene/OrthographicFrustum',[
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Matrix4',
'./CullingVolume'
], function(
Cartesian3,
Cartesian4,
defined,
defineProperties,
DeveloperError,
Matrix4,
CullingVolume) {
'use strict';
/**
* The viewing frustum is defined by 6 planes.
* Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
* define the unit vector normal to the plane, and the w component is the distance of the
* plane from the origin/camera position.
*
* @alias OrthographicFrustum
* @constructor
*
* @example
* var maxRadii = ellipsoid.maximumRadius;
*
* var frustum = new Cesium.OrthographicFrustum();
* frustum.right = maxRadii * Cesium.Math.PI;
* frustum.left = -c.frustum.right;
* frustum.top = c.frustum.right * (canvas.clientHeight / canvas.clientWidth);
* frustum.bottom = -c.frustum.top;
* frustum.near = 0.01 * maxRadii;
* frustum.far = 50.0 * maxRadii;
*/
function OrthographicFrustum() {
/**
* The left clipping plane.
* @type {Number}
* @default undefined
*/
this.left = undefined;
this._left = undefined;
/**
* The right clipping plane.
* @type {Number}
* @default undefined
*/
this.right = undefined;
this._right = undefined;
/**
* The top clipping plane.
* @type {Number}
* @default undefined
*/
this.top = undefined;
this._top = undefined;
/**
* The bottom clipping plane.
* @type {Number}
* @default undefined
*/
this.bottom = undefined;
this._bottom = undefined;
/**
* The distance of the near plane.
* @type {Number}
* @default 1.0
*/
this.near = 1.0;
this._near = this.near;
/**
* The distance of the far plane.
* @type {Number}
* @default 500000000.0;
*/
this.far = 500000000.0;
this._far = this.far;
this._cullingVolume = new CullingVolume();
this._orthographicMatrix = new Matrix4();
}
function update(frustum) {
if (!defined(frustum.right) || !defined(frustum.left) ||
!defined(frustum.top) || !defined(frustum.bottom) ||
!defined(frustum.near) || !defined(frustum.far)) {
throw new DeveloperError('right, left, top, bottom, near, or far parameters are not set.');
}
if (frustum.top !== frustum._top || frustum.bottom !== frustum._bottom ||
frustum.left !== frustum._left || frustum.right !== frustum._right ||
frustum.near !== frustum._near || frustum.far !== frustum._far) {
if (frustum.left > frustum.right) {
throw new DeveloperError('right must be greater than left.');
}
if (frustum.bottom > frustum.top) {
throw new DeveloperError('top must be greater than bottom.');
}
if (frustum.near <= 0 || frustum.near > frustum.far) {
throw new DeveloperError('near must be greater than zero and less than far.');
}
frustum._left = frustum.left;
frustum._right = frustum.right;
frustum._top = frustum.top;
frustum._bottom = frustum.bottom;
frustum._near = frustum.near;
frustum._far = frustum.far;
frustum._orthographicMatrix = Matrix4.computeOrthographicOffCenter(frustum.left, frustum.right, frustum.bottom, frustum.top, frustum.near, frustum.far, frustum._orthographicMatrix);
}
}
defineProperties(OrthographicFrustum.prototype, {
/**
* Gets the orthographic projection matrix computed from the view frustum.
* @memberof OrthographicFrustum.prototype
* @type {Matrix4}
* @readonly
*/
projectionMatrix : {
get : function() {
update(this);
return this._orthographicMatrix;
}
}
});
var getPlanesRight = new Cartesian3();
var getPlanesNearCenter = new Cartesian3();
var getPlanesPoint = new Cartesian3();
var negateScratch = new Cartesian3();
/**
* Creates a culling volume for this frustum.
*
* @param {Cartesian3} position The eye position.
* @param {Cartesian3} direction The view direction.
* @param {Cartesian3} up The up direction.
* @returns {CullingVolume} A culling volume at the given position and orientation.
*
* @example
* // Check if a bounding volume intersects the frustum.
* var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
* var intersect = cullingVolume.computeVisibility(boundingVolume);
*/
OrthographicFrustum.prototype.computeCullingVolume = function(position, direction, up) {
if (!defined(position)) {
throw new DeveloperError('position is required.');
}
if (!defined(direction)) {
throw new DeveloperError('direction is required.');
}
if (!defined(up)) {
throw new DeveloperError('up is required.');
}
var planes = this._cullingVolume.planes;
var t = this.top;
var b = this.bottom;
var r = this.right;
var l = this.left;
var n = this.near;
var f = this.far;
var right = Cartesian3.cross(direction, up, getPlanesRight);
var nearCenter = getPlanesNearCenter;
Cartesian3.multiplyByScalar(direction, n, nearCenter);
Cartesian3.add(position, nearCenter, nearCenter);
var point = getPlanesPoint;
// Left plane
Cartesian3.multiplyByScalar(right, l, point);
Cartesian3.add(nearCenter, point, point);
var plane = planes[0];
if (!defined(plane)) {
plane = planes[0] = new Cartesian4();
}
plane.x = right.x;
plane.y = right.y;
plane.z = right.z;
plane.w = -Cartesian3.dot(right, point);
// Right plane
Cartesian3.multiplyByScalar(right, r, point);
Cartesian3.add(nearCenter, point, point);
plane = planes[1];
if (!defined(plane)) {
plane = planes[1] = new Cartesian4();
}
plane.x = -right.x;
plane.y = -right.y;
plane.z = -right.z;
plane.w = -Cartesian3.dot(Cartesian3.negate(right, negateScratch), point);
// Bottom plane
Cartesian3.multiplyByScalar(up, b, point);
Cartesian3.add(nearCenter, point, point);
plane = planes[2];
if (!defined(plane)) {
plane = planes[2] = new Cartesian4();
}
plane.x = up.x;
plane.y = up.y;
plane.z = up.z;
plane.w = -Cartesian3.dot(up, point);
// Top plane
Cartesian3.multiplyByScalar(up, t, point);
Cartesian3.add(nearCenter, point, point);
plane = planes[3];
if (!defined(plane)) {
plane = planes[3] = new Cartesian4();
}
plane.x = -up.x;
plane.y = -up.y;
plane.z = -up.z;
plane.w = -Cartesian3.dot(Cartesian3.negate(up, negateScratch), point);
// Near plane
plane = planes[4];
if (!defined(plane)) {
plane = planes[4] = new Cartesian4();
}
plane.x = direction.x;
plane.y = direction.y;
plane.z = direction.z;
plane.w = -Cartesian3.dot(direction, nearCenter);
// Far plane
Cartesian3.multiplyByScalar(direction, f, point);
Cartesian3.add(position, point, point);
plane = planes[5];
if (!defined(plane)) {
plane = planes[5] = new Cartesian4();
}
plane.x = -direction.x;
plane.y = -direction.y;
plane.z = -direction.z;
plane.w = -Cartesian3.dot(Cartesian3.negate(direction, negateScratch), point);
return this._cullingVolume;
};
/**
* Returns the pixel's width and height in meters.
*
* @param {Number} drawingBufferWidth The width of the drawing buffer.
* @param {Number} drawingBufferHeight The height of the drawing buffer.
* @param {Number} distance The distance to the near plane in meters.
* @param {Cartesian2} result The object onto which to store the result.
* @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
*
* @exception {DeveloperError} drawingBufferWidth must be greater than zero.
* @exception {DeveloperError} drawingBufferHeight must be greater than zero.
*
* @example
* // Example 1
* // Get the width and height of a pixel.
* var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 0.0, new Cesium.Cartesian2());
*/
OrthographicFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) {
update(this);
if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) {
throw new DeveloperError('Both drawingBufferWidth and drawingBufferHeight are required.');
}
if (drawingBufferWidth <= 0) {
throw new DeveloperError('drawingBufferWidth must be greater than zero.');
}
if (drawingBufferHeight <= 0) {
throw new DeveloperError('drawingBufferHeight must be greater than zero.');
}
if (!defined(distance)) {
throw new DeveloperError('distance is required.');
}
if (!defined(result)) {
throw new DeveloperError('A result object is required.');
}
var frustumWidth = this.right - this.left;
var frustumHeight = this.top - this.bottom;
var pixelWidth = frustumWidth / drawingBufferWidth;
var pixelHeight = frustumHeight / drawingBufferHeight;
result.x = pixelWidth;
result.y = pixelHeight;
return result;
};
/**
* Returns a duplicate of a OrthographicFrustum instance.
*
* @param {OrthographicFrustum} [result] The object onto which to store the result.
* @returns {OrthographicFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
*/
OrthographicFrustum.prototype.clone = function(result) {
if (!defined(result)) {
result = new OrthographicFrustum();
}
result.left = this.left;
result.right = this.right;
result.top = this.top;
result.bottom = this.bottom;
result.near = this.near;
result.far = this.far;
// force update of clone to compute matrices
result._left = undefined;
result._right = undefined;
result._top = undefined;
result._bottom = undefined;
result._near = undefined;
result._far = undefined;
return result;
};
/**
* Compares the provided OrthographicFrustum componentwise and returns
* true
if they are equal, false
otherwise.
*
* @param {OrthographicFrustum} [other] The right hand side OrthographicFrustum.
* @returns {Boolean} true
if they are equal, false
otherwise.
*/
OrthographicFrustum.prototype.equals = function(other) {
return (defined(other) &&
this.right === other.right &&
this.left === other.left &&
this.top === other.top &&
this.bottom === other.bottom &&
this.near === other.near &&
this.far === other.far);
};
return OrthographicFrustum;
});
/*global define*/
define('Widgets/getElement',[
'../Core/DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* If element is a string, look up the element in the DOM by ID. Otherwise return element.
*
* @private
*
* @exception {DeveloperError} Element with id "id" does not exist in the document.
*/
function getElement(element) {
if (typeof element === 'string') {
var foundElement = document.getElementById(element);
if (foundElement === null) {
throw new DeveloperError('Element with id "' + element + '" does not exist in the document.');
}
element = foundElement;
}
return element;
}
return getElement;
});
/*global define*/
define('Scene/PerformanceDisplay',[
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/getTimestamp',
'../Widgets/getElement'
], function(
defaultValue,
defined,
destroyObject,
DeveloperError,
getTimestamp,
getElement) {
'use strict';
/**
* @private
*/
function PerformanceDisplay(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var container = getElement(options.container);
if (!defined(container)) {
throw new DeveloperError('container is required');
}
this._container = container;
var display = document.createElement('div');
display.className = 'cesium-performanceDisplay';
var fpsElement = document.createElement('div');
fpsElement.className = 'cesium-performanceDisplay-fps';
this._fpsText = document.createTextNode("");
fpsElement.appendChild(this._fpsText);
var msElement = document.createElement('div');
msElement.className = 'cesium-performanceDisplay-ms';
this._msText = document.createTextNode("");
msElement.appendChild(this._msText);
display.appendChild(msElement);
display.appendChild(fpsElement);
this._container.appendChild(display);
this._lastFpsSampleTime = undefined;
this._frameCount = 0;
this._time = undefined;
this._fps = 0;
this._frameTime = 0;
}
/**
* Update the display. This function should only be called once per frame, because
* each call records a frame in the internal buffer and redraws the display.
*/
PerformanceDisplay.prototype.update = function() {
if (!defined(this._time)) {
//first update
this._lastFpsSampleTime = getTimestamp();
this._time = getTimestamp();
return;
}
var previousTime = this._time;
var time = getTimestamp();
this._time = time;
var frameTime = time - previousTime;
this._frameCount++;
var fps = this._fps;
var fpsElapsedTime = time - this._lastFpsSampleTime;
if (fpsElapsedTime > 1000) {
fps = this._frameCount * 1000 / fpsElapsedTime | 0;
this._lastFpsSampleTime = time;
this._frameCount = 0;
}
if (fps !== this._fps) {
this._fpsText.nodeValue = fps + ' FPS';
this._fps = fps;
}
if (frameTime !== this._frameTime) {
this._msText.nodeValue = frameTime.toFixed(2) + ' MS';
this._frameTime = frameTime;
}
};
/**
* Destroys the WebGL resources held by this object.
*/
PerformanceDisplay.prototype.destroy = function() {
return destroyObject(this);
};
return PerformanceDisplay;
});
/*global define*/
define('Scene/PickDepth',[
'../Core/defined',
'../Core/destroyObject',
'../Core/PixelFormat',
'../Renderer/Framebuffer',
'../Renderer/PixelDatatype',
'../Renderer/RenderState',
'../Renderer/Texture'
], function(
defined,
destroyObject,
PixelFormat,
Framebuffer,
PixelDatatype,
RenderState,
Texture) {
'use strict';
/**
* @private
*/
function PickDepth() {
this.framebuffer = undefined;
this._depthTexture = undefined;
this._textureToCopy = undefined;
this._copyDepthCommand = undefined;
this._debugPickDepthViewportCommand = undefined;
}
function executeDebugPickDepth(pickDepth, context, passState) {
if (!defined(pickDepth._debugPickDepthViewportCommand)) {
var fs =
'uniform sampler2D u_texture;\n' +
'varying vec2 v_textureCoordinates;\n' +
'void main()\n' +
'{\n' +
' float z_window = czm_unpackDepth(texture2D(u_texture, v_textureCoordinates));\n' +
' float n_range = czm_depthRange.near;\n' +
' float f_range = czm_depthRange.far;\n' +
' float z_ndc = (2.0 * z_window - n_range - f_range) / (f_range - n_range);\n' +
' float scale = pow(z_ndc * 0.5 + 0.5, 8.0);\n' +
' gl_FragColor = vec4(mix(vec3(0.0), vec3(1.0), scale), 1.0);\n' +
'}\n';
pickDepth._debugPickDepthViewportCommand = context.createViewportQuadCommand(fs, {
uniformMap : {
u_texture : function() {
return pickDepth._depthTexture;
}
},
owner : pickDepth
});
}
pickDepth._debugPickDepthViewportCommand.execute(context, passState);
}
function destroyTextures(pickDepth) {
pickDepth._depthTexture = pickDepth._depthTexture && !pickDepth._depthTexture.isDestroyed() && pickDepth._depthTexture.destroy();
}
function destroyFramebuffers(pickDepth) {
pickDepth.framebuffer = pickDepth.framebuffer && !pickDepth.framebuffer.isDestroyed() && pickDepth.framebuffer.destroy();
}
function createTextures(pickDepth, context, width, height) {
pickDepth._depthTexture = new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE
});
}
function createFramebuffers(pickDepth, context, width, height) {
destroyTextures(pickDepth);
destroyFramebuffers(pickDepth);
createTextures(pickDepth, context, width, height);
pickDepth.framebuffer = new Framebuffer({
context : context,
colorTextures : [pickDepth._depthTexture],
destroyAttachments : false
});
}
function updateFramebuffers(pickDepth, context, depthTexture) {
var width = depthTexture.width;
var height = depthTexture.height;
var texture = pickDepth._depthTexture;
var textureChanged = !defined(texture) || texture.width !== width || texture.height !== height;
if (!defined(pickDepth.framebuffer) || textureChanged) {
createFramebuffers(pickDepth, context, width, height);
}
}
function updateCopyCommands(pickDepth, context, depthTexture) {
if (!defined(pickDepth._copyDepthCommand)) {
var fs =
'uniform sampler2D u_texture;\n' +
'varying vec2 v_textureCoordinates;\n' +
'void main()\n' +
'{\n' +
' gl_FragColor = czm_packDepth(texture2D(u_texture, v_textureCoordinates).r);\n' +
'}\n';
pickDepth._copyDepthCommand = context.createViewportQuadCommand(fs, {
renderState : RenderState.fromCache(),
uniformMap : {
u_texture : function() {
return pickDepth._textureToCopy;
}
},
owner : pickDepth
});
}
pickDepth._textureToCopy = depthTexture;
pickDepth._copyDepthCommand.framebuffer = pickDepth.framebuffer;
}
PickDepth.prototype.executeDebugPickDepth = function(context, passState) {
executeDebugPickDepth(this, context, passState);
};
PickDepth.prototype.update = function(context, depthTexture) {
updateFramebuffers(this, context, depthTexture);
updateCopyCommands(this, context, depthTexture);
};
PickDepth.prototype.executeCopyDepth = function(context, passState) {
this._copyDepthCommand.execute(context, passState);
};
PickDepth.prototype.isDestroyed = function() {
return false;
};
PickDepth.prototype.destroy = function() {
destroyTextures(this);
destroyFramebuffers(this);
this._copyDepthCommand.shaderProgram = defined(this._copyDepthCommand.shaderProgram) && this._copyDepthCommand.shaderProgram.destroy();
return destroyObject(this);
};
return PickDepth;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PointAppearanceFS',[],function() {
'use strict';
return "uniform vec4 highlightColor;\n\
varying vec3 v_color;\n\
void main()\n\
{\n\
gl_FragColor = vec4(v_color * highlightColor.rgb, highlightColor.a);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PointAppearanceVS',[],function() {
'use strict';
return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 color;\n\
attribute float batchId;\n\
uniform float pointSize;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_color;\n\
void main()\n\
{\n\
v_color = color;\n\
gl_Position = czm_modelViewProjectionRelativeToEye * czm_computePosition();\n\
gl_PointSize = pointSize;\n\
}\n\
";
});
/*global define*/
define('Scene/PointAppearance',[
'../Core/Color',
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/VertexFormat',
'../Shaders/Appearances/PointAppearanceFS',
'../Shaders/Appearances/PointAppearanceVS',
'./Appearance'
], function(
Color,
combine,
defaultValue,
defined,
defineProperties,
VertexFormat,
PointAppearanceFS,
PointAppearanceVS,
Appearance) {
'use strict';
/**
* An appearance for point geometry {@link PointGeometry}
*
* @alias PointAppearance
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.translucent=false] When true
, the geometry is expected to appear translucent so {@link PointAppearance#renderState} has alpha blending enabled.
* @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
* @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
* @param {RenderState} [options.renderState] Optional render state to override the default render state.
* @param {Object} [options.uniforms] Additional uniforms that are used by the vertex and fragment shaders.
* @param {Number} [options.pointSize] Point size in pixels.
* @param {Color} [options.highlightColor] Color multiplier in the fragment shader. The alpha channel is used for alpha blending when translucency is enabled.
*
* @example
* var primitive = new Cesium.Primitive({
* geometryInstances : new Cesium.GeometryInstance({
* geometry : new Cesium.PointGeometry({
* positionsTypedArray : positions,
* colorsTypedArray : colors,
* boundingSphere : boundingSphere
* })
* }),
* appearance : new Cesium.PointAppearance({
* translucent : true
* })
* });
*
* @private
*/
function PointAppearance(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._vertexShaderSource = defaultValue(options.vertexShaderSource, PointAppearanceVS);
this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, PointAppearanceFS);
this._renderState = Appearance.getDefaultRenderState(false, false, options.renderState);
this._pointSize = defaultValue(options.pointSize, 2.0);
this._highlightColor = defined(options.highlightColor) ? options.highlightColor : new Color();
/**
* This property is part of the {@link Appearance} interface, but is not
* used by {@link PointAppearance} since a fully custom fragment shader is used.
*
* @type Material
*
* @default undefined
*/
this.material = undefined;
/**
* When true
, the geometry is expected to appear translucent.
*
* @type {Boolean}
*
* @default false
*/
this.translucent = defaultValue(options.translucent, false);
/**
* @private
*/
this.uniforms = {
highlightColor : this._highlightColor,
pointSize : this._pointSize
};
// Combine default uniforms and additional uniforms
var optionsUniforms = options.uniforms;
this.uniforms = combine(this.uniforms, optionsUniforms, true);
}
defineProperties(PointAppearance.prototype, {
/**
* The GLSL source code for the vertex shader.
*
* @memberof PointAppearance.prototype
*
* @type {String}
* @readonly
*/
vertexShaderSource : {
get : function() {
return this._vertexShaderSource;
}
},
/**
* The GLSL source code for the fragment shader. The full fragment shader
* source is built procedurally taking into account the {@link PointAppearance#material}.
* Use {@link PointAppearance#getFragmentShaderSource} to get the full source.
*
* @memberof PointAppearance.prototype
*
* @type {String}
* @readonly
*/
fragmentShaderSource : {
get : function() {
return this._fragmentShaderSource;
}
},
/**
* The WebGL fixed-function state to use when rendering the geometry.
*
* @memberof PointAppearance.prototype
*
* @type {Object}
* @readonly
*/
renderState : {
get : function() {
return this._renderState;
}
},
/**
* When true
, the geometry is expected to be closed.
*
* @memberof PointAppearance.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
closed : {
get : function() {
return false;
}
},
/**
* The {@link VertexFormat} that this appearance instance is compatible with.
* A geometry can have more vertex attributes and still be compatible - at a
* potential performance cost - but it can't have less.
*
* @memberof PointAppearance.prototype
*
* @type VertexFormat
* @readonly
*
* @default {@link PointAppearance.VERTEX_FORMAT}
*/
vertexFormat : {
get : function() {
return PointAppearance.VERTEX_FORMAT;
}
},
/**
* The size in pixels used when rendering the primitive. This helps calculate an accurate
* bounding volume for point rendering and other appearances that are defined in pixel sizes.
*
* @memberof PointAppearance.prototype
*
* @type {Number}
* @readonly
*/
pixelSize : {
get : function() {
return this._pointSize;
}
}
});
/**
* The {@link VertexFormat} that all {@link PointAppearance} instances
* are compatible with, which requires only position
and color
* attributes. Other attributes are procedurally computed in the fragment shader.
*
* @type VertexFormat
*
* @constant
*/
PointAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_COLOR;
/**
* Returns the full GLSL fragment shader source, which for {@link PointAppearance} is just
* {@link PointAppearance#fragmentShaderSource}.
*
* @function
*
* @returns {String} The full GLSL fragment shader source.
*/
PointAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;
/**
* Determines if the geometry is translucent based on {@link PointAppearance#translucent}.
*
* @function
*
* @returns {Boolean} true
if the appearance is translucent.
*/
PointAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;
/**
* Creates a render state. This is not the final render state instance; instead,
* it can contain a subset of render state properties identical to the render state
* created in the context.
*
* @function
*
* @returns {Object} The render state.
*/
PointAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
return PointAppearance;
});
/*global define*/
define('Scene/PrimitiveCollection',[
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError'
], function(
createGuid,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError) {
'use strict';
/**
* A collection of primitives. This is most often used with {@link Scene#primitives},
* but PrimitiveCollection
is also a primitive itself so collections can
* be added to collections forming a hierarchy.
*
* @alias PrimitiveCollection
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Boolean} [options.show=true] Determines if the primitives in the collection will be shown.
* @param {Boolean} [options.destroyPrimitives=true] Determines if primitives in the collection are destroyed when they are removed.
*
* @example
* var billboards = new Cesium.BillboardCollection();
* var labels = new Cesium.LabelCollection();
*
* var collection = new Cesium.PrimitiveCollection();
* collection.add(billboards);
*
* scene.primitives.add(collection); // Add collection
* scene.primitives.add(labels); // Add regular primitive
*/
function PrimitiveCollection(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._primitives = [];
this._guid = createGuid();
/**
* Determines if primitives in this collection will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* Determines if primitives in the collection are destroyed when they are removed by
* {@link PrimitiveCollection#destroy} or {@link PrimitiveCollection#remove} or implicitly
* by {@link PrimitiveCollection#removeAll}.
*
* @type {Boolean}
* @default true
*
* @example
* // Example 1. Primitives are destroyed by default.
* var primitives = new Cesium.PrimitiveCollection();
* var labels = primitives.add(new Cesium.LabelCollection());
* primitives = primitives.destroy();
* var b = labels.isDestroyed(); // true
*
* @example
* // Example 2. Do not destroy primitives in a collection.
* var primitives = new Cesium.PrimitiveCollection();
* primitives.destroyPrimitives = false;
* var labels = primitives.add(new Cesium.LabelCollection());
* primitives = primitives.destroy();
* var b = labels.isDestroyed(); // false
* labels = labels.destroy(); // explicitly destroy
*/
this.destroyPrimitives = defaultValue(options.destroyPrimitives, true);
}
defineProperties(PrimitiveCollection.prototype, {
/**
* Gets the number of primitives in the collection.
*
* @memberof PrimitiveCollection.prototype
*
* @type {Number}
* @readonly
*/
length : {
get : function() {
return this._primitives.length;
}
}
});
/**
* Adds a primitive to the collection.
*
* @param {Object} primitive The primitive to add.
* @returns {Object} The primitive added to the collection.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* var billboards = scene.primitives.add(new Cesium.BillboardCollection());
*/
PrimitiveCollection.prototype.add = function(primitive) {
if (!defined(primitive)) {
throw new DeveloperError('primitive is required.');
}
var external = (primitive._external = primitive._external || {});
var composites = (external._composites = external._composites || {});
composites[this._guid] = {
collection : this
};
this._primitives.push(primitive);
return primitive;
};
/**
* Removes a primitive from the collection.
*
* @param {Object} [primitive] The primitive to remove.
* @returns {Boolean} true
if the primitive was removed; false
if the primitive is undefined
or was not found in the collection.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* var billboards = scene.primitives.add(new Cesium.BillboardCollection());
* scene.primitives.remove(p); // Returns true
*
* @see PrimitiveCollection#destroyPrimitives
*/
PrimitiveCollection.prototype.remove = function(primitive) {
// PERFORMANCE_IDEA: We can obviously make this a lot faster.
if (this.contains(primitive)) {
var index = this._primitives.indexOf(primitive);
if (index !== -1) {
this._primitives.splice(index, 1);
delete primitive._external._composites[this._guid];
if (this.destroyPrimitives) {
primitive.destroy();
}
return true;
}
// else ... this is not possible, I swear.
}
return false;
};
/**
* Removes and destroys a primitive, regardless of destroyPrimitives setting.
* @private
*/
PrimitiveCollection.prototype.removeAndDestroy = function(primitive) {
var removed = this.remove(primitive);
if (removed && !this.destroyPrimitives) {
primitive.destroy();
}
return removed;
};
/**
* Removes all primitives in the collection.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see PrimitiveCollection#destroyPrimitives
*/
PrimitiveCollection.prototype.removeAll = function() {
if (this.destroyPrimitives) {
var primitives = this._primitives;
var length = primitives.length;
for ( var i = 0; i < length; ++i) {
primitives[i].destroy();
}
}
this._primitives = [];
};
/**
* Determines if this collection contains a primitive.
*
* @param {Object} [primitive] The primitive to check for.
* @returns {Boolean} true
if the primitive is in the collection; false
if the primitive is undefined
or was not found in the collection.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see PrimitiveCollection#get
*/
PrimitiveCollection.prototype.contains = function(primitive) {
return !!(defined(primitive) &&
primitive._external &&
primitive._external._composites &&
primitive._external._composites[this._guid]);
};
function getPrimitiveIndex(compositePrimitive, primitive) {
if (!compositePrimitive.contains(primitive)) {
throw new DeveloperError('primitive is not in this collection.');
}
return compositePrimitive._primitives.indexOf(primitive);
}
/**
* Raises a primitive "up one" in the collection. If all primitives in the collection are drawn
* on the globe surface, this visually moves the primitive up one.
*
* @param {Object} [primitive] The primitive to raise.
*
* @exception {DeveloperError} primitive is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see PrimitiveCollection#raiseToTop
* @see PrimitiveCollection#lower
* @see PrimitiveCollection#lowerToBottom
*/
PrimitiveCollection.prototype.raise = function(primitive) {
if (defined(primitive)) {
var index = getPrimitiveIndex(this, primitive);
var primitives = this._primitives;
if (index !== primitives.length - 1) {
var p = primitives[index];
primitives[index] = primitives[index + 1];
primitives[index + 1] = p;
}
}
};
/**
* Raises a primitive to the "top" of the collection. If all primitives in the collection are drawn
* on the globe surface, this visually moves the primitive to the top.
*
* @param {Object} [primitive] The primitive to raise the top.
*
* @exception {DeveloperError} primitive is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see PrimitiveCollection#raise
* @see PrimitiveCollection#lower
* @see PrimitiveCollection#lowerToBottom
*/
PrimitiveCollection.prototype.raiseToTop = function(primitive) {
if (defined(primitive)) {
var index = getPrimitiveIndex(this, primitive);
var primitives = this._primitives;
if (index !== primitives.length - 1) {
// PERFORMANCE_IDEA: Could be faster
primitives.splice(index, 1);
primitives.push(primitive);
}
}
};
/**
* Lowers a primitive "down one" in the collection. If all primitives in the collection are drawn
* on the globe surface, this visually moves the primitive down one.
*
* @param {Object} [primitive] The primitive to lower.
*
* @exception {DeveloperError} primitive is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see PrimitiveCollection#lowerToBottom
* @see PrimitiveCollection#raise
* @see PrimitiveCollection#raiseToTop
*/
PrimitiveCollection.prototype.lower = function(primitive) {
if (defined(primitive)) {
var index = getPrimitiveIndex(this, primitive);
var primitives = this._primitives;
if (index !== 0) {
var p = primitives[index];
primitives[index] = primitives[index - 1];
primitives[index - 1] = p;
}
}
};
/**
* Lowers a primitive to the "bottom" of the collection. If all primitives in the collection are drawn
* on the globe surface, this visually moves the primitive to the bottom.
*
* @param {Object} [primitive] The primitive to lower to the bottom.
*
* @exception {DeveloperError} primitive is not in this collection.
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @see PrimitiveCollection#lower
* @see PrimitiveCollection#raise
* @see PrimitiveCollection#raiseToTop
*/
PrimitiveCollection.prototype.lowerToBottom = function(primitive) {
if (defined(primitive)) {
var index = getPrimitiveIndex(this, primitive);
var primitives = this._primitives;
if (index !== 0) {
// PERFORMANCE_IDEA: Could be faster
primitives.splice(index, 1);
primitives.unshift(primitive);
}
}
};
/**
* Returns the primitive in the collection at the specified index.
*
* @param {Number} index The zero-based index of the primitive to return.
* @returns {Object} The primitive at the index
.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* // Toggle the show property of every primitive in the collection.
* var primitives = scene.primitives;
* var length = primitives.length;
* for (var i = 0; i < length; ++i) {
* var p = primitives.get(i);
* p.show = !p.show;
* }
*
* @see PrimitiveCollection#length
*/
PrimitiveCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._primitives[index];
};
/**
* @private
*/
PrimitiveCollection.prototype.update = function(frameState) {
if (!this.show) {
return;
}
var primitives = this._primitives;
// Using primitives.length in the loop is a temporary workaround
// to allow quadtree updates to add and remove primitives in
// update(). This will be changed to manage added and removed lists.
for (var i = 0; i < primitives.length; ++i) {
primitives[i].update(frameState);
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see PrimitiveCollection#destroy
*/
PrimitiveCollection.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by each primitive in this collection. Explicitly destroying this
* collection allows for deterministic release of WebGL resources, instead of relying on the garbage
* collector to destroy this collection.
*
* Since destroying a collection destroys all the contained primitives, only destroy a collection
* when you are sure no other code is still using any of the contained primitives.
*
* Once this collection is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* primitives = primitives && primitives.destroy();
*
* @see PrimitiveCollection#isDestroyed
*/
PrimitiveCollection.prototype.destroy = function() {
this.removeAll();
return destroyObject(this);
};
return PrimitiveCollection;
});
/*global define*/
define('Scene/QuadtreeTileProvider',[
'../Core/defineProperties',
'../Core/DeveloperError'
], function(
defineProperties,
DeveloperError) {
'use strict';
/**
* Provides general quadtree tiles to be displayed on or near the surface of an ellipsoid. It is intended to be
* used with the {@link QuadtreePrimitive}. This type describes an interface and is not intended to be
* instantiated directly.
*
* @alias QuadtreeTileProvider
* @constructor
* @private
*/
function QuadtreeTileProvider() {
DeveloperError.throwInstantiationError();
}
/**
* Computes the default geometric error for level zero of the quadtree.
*
* @memberof QuadtreeTileProvider
*
* @param {TilingScheme} tilingScheme The tiling scheme for which to compute the geometric error.
* @returns {Number} The maximum geometric error at level zero, in meters.
*/
QuadtreeTileProvider.computeDefaultLevelZeroMaximumGeometricError = function(tilingScheme) {
return tilingScheme.ellipsoid.maximumRadius * 2 * Math.PI * 0.25 / (65 * tilingScheme.getNumberOfXTilesAtLevel(0));
};
defineProperties(QuadtreeTileProvider.prototype, {
/**
* Gets or sets the {@link QuadtreePrimitive} for which this provider is
* providing tiles.
* @memberof QuadtreeTileProvider.prototype
* @type {QuadtreePrimitive}
*/
quadtree : {
get : DeveloperError.throwInstantiationError,
set : DeveloperError.throwInstantiationError
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof QuadtreeTileProvider.prototype
* @type {Boolean}
*/
ready : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets the tiling scheme used by the provider. This property should
* not be accessed before {@link QuadtreeTileProvider#ready} returns true.
* @memberof QuadtreeTileProvider.prototype
* @type {TilingScheme}
*/
tilingScheme : {
get : DeveloperError.throwInstantiationError
},
/**
* Gets an event that is raised when the geometry provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof QuadtreeTileProvider.prototype
* @type {Event}
*/
errorEvent : {
get : DeveloperError.throwInstantiationError
}
});
/**
* Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
* or any other functions.
* @memberof QuadtreeTileProvider
* @function
*
* @param {Context} context The rendering context.
* @param {FrameState} frameState The frame state.
* @param {DrawCommand[]} commandList An array of rendering commands. This method may push
* commands into this array.
*/
QuadtreeTileProvider.prototype.beginUpdate = DeveloperError.throwInstantiationError;
/**
* Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame}
* and any other functions.
* @memberof QuadtreeTileProvider
* @function
*
* @param {Context} context The rendering context.
* @param {FrameState} frameState The frame state.
* @param {DrawCommand[]} commandList An array of rendering commands. This method may push
* commands into this array.
*/
QuadtreeTileProvider.prototype.endUpdate = DeveloperError.throwInstantiationError;
/**
* Gets the maximum geometric error allowed in a tile at a given level, in meters. This function should not be
* called before {@link QuadtreeTileProvider#ready} returns true.
*
* @see {QuadtreeTileProvider.computeDefaultLevelZeroMaximumGeometricError}
*
* @memberof QuadtreeTileProvider
* @function
*
* @param {Number} level The tile level for which to get the maximum geometric error.
* @returns {Number} The maximum geometric error in meters.
*/
QuadtreeTileProvider.prototype.getLevelMaximumGeometricError = DeveloperError.throwInstantiationError;
/**
* Loads, or continues loading, a given tile. This function will continue to be called
* until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}. This function should
* not be called before {@link QuadtreeTileProvider#ready} returns true.
*
* @memberof QuadtreeTileProvider
* @function
*
* @param {Context} context The rendering context.
* @param {FrameState} frameState The frame state.
* @param {QuadtreeTile} tile The tile to load.
*
* @exception {DeveloperError} loadTile
must not be called before the tile provider is ready.
*/
QuadtreeTileProvider.prototype.loadTile = DeveloperError.throwInstantiationError;
/**
* Determines the visibility of a given tile. The tile may be fully visible, partially visible, or not
* visible at all. Tiles that are renderable and are at least partially visible will be shown by a call
* to {@link QuadtreeTileProvider#showTileThisFrame}.
*
* @memberof QuadtreeTileProvider
*
* @param {QuadtreeTile} tile The tile instance.
* @param {FrameState} frameState The state information about the current frame.
* @param {QuadtreeOccluders} occluders The objects that may occlude this tile.
*
* @returns {Visibility} The visibility of the tile.
*/
QuadtreeTileProvider.prototype.computeTileVisibility = DeveloperError.throwInstantiationError;
/**
* Shows a specified tile in this frame. The provider can cause the tile to be shown by adding
* render commands to the commandList, or use any other method as appropriate. The tile is not
* expected to be visible next frame as well, unless this method is call next frame, too.
*
* @memberof QuadtreeTileProvider
* @function
*
* @param {QuadtreeTile} tile The tile instance.
* @param {Context} context The rendering context.
* @param {FrameState} frameState The state information of the current rendering frame.
* @param {DrawCommand[]} commandList The list of rendering commands. This method may add additional commands to this list.
*/
QuadtreeTileProvider.prototype.showTileThisFrame = DeveloperError.throwInstantiationError;
/**
* Gets the distance from the camera to the closest point on the tile. This is used for level-of-detail selection.
*
* @memberof QuadtreeTileProvider
* @function
*
* @param {QuadtreeTile} tile The tile instance.
* @param {FrameState} frameState The state information of the current rendering frame.
*
* @returns {Number} The distance from the camera to the closest point on the tile, in meters.
*/
QuadtreeTileProvider.prototype.computeDistanceToTile = DeveloperError.throwInstantiationError;
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @memberof QuadtreeTileProvider
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see QuadtreeTileProvider#destroy
*/
QuadtreeTileProvider.prototype.isDestroyed = DeveloperError.throwInstantiationError;
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @memberof QuadtreeTileProvider
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* provider = provider && provider();
*
* @see QuadtreeTileProvider#isDestroyed
*/
QuadtreeTileProvider.prototype.destroy = DeveloperError.throwInstantiationError;
return QuadtreeTileProvider;
});
/*global define*/
define('Scene/SceneTransitioner',[
'../Core/Cartesian3',
'../Core/Cartographic',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EasingFunction',
'../Core/Math',
'../Core/Matrix4',
'../Core/Ray',
'../Core/ScreenSpaceEventHandler',
'../Core/ScreenSpaceEventType',
'../Core/Transforms',
'./Camera',
'./OrthographicFrustum',
'./PerspectiveFrustum',
'./SceneMode'
], function(
Cartesian3,
Cartographic,
defined,
destroyObject,
DeveloperError,
EasingFunction,
CesiumMath,
Matrix4,
Ray,
ScreenSpaceEventHandler,
ScreenSpaceEventType,
Transforms,
Camera,
OrthographicFrustum,
PerspectiveFrustum,
SceneMode) {
'use strict';
/**
* @private
*/
function SceneTransitioner(scene) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
this._scene = scene;
this._currentTweens = [];
this._morphHandler = undefined;
this._morphCancelled = false;
this._completeMorph = undefined;
}
SceneTransitioner.prototype.completeMorph = function() {
if (defined(this._completeMorph)) {
this._completeMorph();
}
};
SceneTransitioner.prototype.morphTo2D = function(duration, ellipsoid) {
if (defined(this._completeMorph)) {
this._completeMorph();
}
var scene = this._scene;
this._previousMode = scene.mode;
if (this._previousMode === SceneMode.SCENE2D || this._previousMode === SceneMode.MORPHING) {
return;
}
this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.SCENE2D, true);
scene._mode = SceneMode.MORPHING;
scene.camera._setTransform(Matrix4.IDENTITY);
if (this._previousMode === SceneMode.COLUMBUS_VIEW) {
morphFromColumbusViewTo2D(this, duration);
} else {
morphFrom3DTo2D(this, duration, ellipsoid);
}
if (duration === 0.0 && defined(this._completeMorph)) {
this._completeMorph();
}
};
var scratchToCVPosition = new Cartesian3();
var scratchToCVDirection = new Cartesian3();
var scratchToCVUp = new Cartesian3();
var scratchToCVPosition2D = new Cartesian3();
var scratchToCVDirection2D = new Cartesian3();
var scratchToCVUp2D = new Cartesian3();
var scratchToCVSurfacePosition = new Cartesian3();
var scratchToCVCartographic = new Cartographic();
var scratchToCVToENU = new Matrix4();
var scratchToCVFrustum = new PerspectiveFrustum();
var scratchToCVCamera = {
position : undefined,
direction : undefined,
up : undefined,
position2D : undefined,
direction2D : undefined,
up2D : undefined,
frustum : undefined
};
SceneTransitioner.prototype.morphToColumbusView = function(duration, ellipsoid) {
if (defined(this._completeMorph)) {
this._completeMorph();
}
var scene = this._scene;
this._previousMode = scene.mode;
if (this._previousMode === SceneMode.COLUMBUS_VIEW || this._previousMode === SceneMode.MORPHING) {
return;
}
this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.COLUMBUS_VIEW, true);
scene.camera._setTransform(Matrix4.IDENTITY);
var position = scratchToCVPosition;
var direction = scratchToCVDirection;
var up = scratchToCVUp;
if (duration > 0.0) {
position.x = 0.0;
position.y = 0.0;
position.z = 5.0 * ellipsoid.maximumRadius;
Cartesian3.negate(Cartesian3.UNIT_Z, direction);
Cartesian3.clone(Cartesian3.UNIT_Y, up);
} else {
var camera = scene.camera;
if (this._previousMode === SceneMode.SCENE2D) {
Cartesian3.clone(camera.position, position);
position.z = camera.frustum.right - camera.frustum.left;
Cartesian3.negate(Cartesian3.UNIT_Z, direction);
Cartesian3.clone(Cartesian3.UNIT_Y, up);
} else {
Cartesian3.clone(camera.positionWC, position);
Cartesian3.clone(camera.directionWC, direction);
Cartesian3.clone(camera.upWC, up);
var surfacePoint = ellipsoid.scaleToGeodeticSurface(position, scratchToCVSurfacePosition);
var toENU = Transforms.eastNorthUpToFixedFrame(surfacePoint, ellipsoid, scratchToCVToENU);
Matrix4.inverseTransformation(toENU, toENU);
scene.mapProjection.project(ellipsoid.cartesianToCartographic(position, scratchToCVCartographic), position);
Matrix4.multiplyByPointAsVector(toENU, direction, direction);
Matrix4.multiplyByPointAsVector(toENU, up, up);
}
}
var frustum = scratchToCVFrustum;
frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
frustum.fov = CesiumMath.toRadians(60.0);
var cameraCV = scratchToCVCamera;
cameraCV.position = position;
cameraCV.direction = direction;
cameraCV.up = up;
cameraCV.frustum = frustum;
var complete = completeColumbusViewCallback(cameraCV);
createMorphHandler(this, complete);
if (this._previousMode === SceneMode.SCENE2D) {
morphFrom2DToColumbusView(this, duration, cameraCV, complete);
} else {
cameraCV.position2D = Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, position, scratchToCVPosition2D);
cameraCV.direction2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, direction, scratchToCVDirection2D);
cameraCV.up2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, up, scratchToCVUp2D);
scene._mode = SceneMode.MORPHING;
morphFrom3DToColumbusView(this, duration, cameraCV, complete);
}
if (duration === 0.0 && defined(this._completeMorph)) {
this._completeMorph();
}
};
SceneTransitioner.prototype.morphTo3D = function(duration, ellipsoid) {
if (defined(this._completeMorph)) {
this._completeMorph();
}
var scene = this._scene;
this._previousMode = scene.mode;
if (this._previousMode === SceneMode.SCENE3D || this._previousMode === SceneMode.MORPHING) {
return;
}
this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.SCENE3D, true);
scene._mode = SceneMode.MORPHING;
scene.camera._setTransform(Matrix4.IDENTITY);
if (this._previousMode === SceneMode.SCENE2D) {
morphFrom2DTo3D(this, duration, ellipsoid);
} else {
var camera3D;
if (duration > 0.0) {
camera3D = scratchCVTo3DCamera;
Cartesian3.fromDegrees(0.0, 0.0, 5.0 * ellipsoid.maximumRadius, ellipsoid, camera3D.position);
Cartesian3.negate(camera3D.position, camera3D.direction);
Cartesian3.normalize(camera3D.direction, camera3D.direction);
Cartesian3.clone(Cartesian3.UNIT_Z, camera3D.up);
} else {
camera3D = getColumbusViewTo3DCamera(this, ellipsoid);
}
var complete = complete3DCallback(camera3D);
createMorphHandler(this, complete);
morphFromColumbusViewTo3D(this, duration, camera3D, complete);
}
if (duration === 0.0 && defined(this._completeMorph)) {
this._completeMorph();
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*/
SceneTransitioner.prototype.isDestroyed = function() {
return false;
};
/**
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
* @example
* transitioner = transitioner && transitioner.destroy();
*/
SceneTransitioner.prototype.destroy = function() {
destroyMorphHandler(this);
return destroyObject(this);
};
function createMorphHandler(transitioner, completeMorphFunction) {
if (transitioner._scene.completeMorphOnUserInput) {
transitioner._morphHandler = new ScreenSpaceEventHandler(transitioner._scene.canvas, false);
var completeMorph = function() {
transitioner._morphCancelled = true;
completeMorphFunction(transitioner);
};
transitioner._completeMorph = completeMorph;
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.LEFT_DOWN);
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.MIDDLE_DOWN);
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.RIGHT_DOWN);
transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.WHEEL);
}
}
function destroyMorphHandler(transitioner) {
var tweens = transitioner._currentTweens;
for ( var i = 0; i < tweens.length; ++i) {
tweens[i].cancelTween();
}
transitioner._currentTweens.length = 0;
transitioner._morphHandler = transitioner._morphHandler && transitioner._morphHandler.destroy();
}
var scratchCVTo3DCartographic = new Cartographic();
var scratchCVTo3DSurfacePoint = new Cartesian3();
var scratchCVTo3DFromENU = new Matrix4();
var scratchCVTo3DCamera = {
position : new Cartesian3(),
direction : new Cartesian3(),
up : new Cartesian3(),
frustum : undefined
};
function getColumbusViewTo3DCamera(transitioner, ellipsoid) {
var scene = transitioner._scene;
var camera = scene.camera;
var camera3D = scratchCVTo3DCamera;
var position = camera3D.position;
var direction = camera3D.direction;
var up = camera3D.up;
var positionCarto = scene.mapProjection.unproject(camera.position, scratchCVTo3DCartographic);
ellipsoid.cartographicToCartesian(positionCarto, position);
var surfacePoint = ellipsoid.scaleToGeodeticSurface(position, scratchCVTo3DSurfacePoint);
var fromENU = Transforms.eastNorthUpToFixedFrame(surfacePoint, ellipsoid, scratchCVTo3DFromENU);
Matrix4.multiplyByPointAsVector(fromENU, camera.direction, direction);
Matrix4.multiplyByPointAsVector(fromENU, camera.up, up);
return camera3D;
}
var scratchCVTo3DStartPos = new Cartesian3();
var scratchCVTo3DStartDir = new Cartesian3();
var scratchCVTo3DStartUp = new Cartesian3();
var scratchCVTo3DEndPos = new Cartesian3();
var scratchCVTo3DEndDir = new Cartesian3();
var scratchCVTo3DEndUp = new Cartesian3();
function morphFromColumbusViewTo3D(transitioner, duration, endCamera, complete) {
duration *= 0.5;
var scene = transitioner._scene;
var camera = scene.camera;
var startPos = Cartesian3.clone(camera.position, scratchCVTo3DStartPos);
var startDir = Cartesian3.clone(camera.direction, scratchCVTo3DStartDir);
var startUp = Cartesian3.clone(camera.up, scratchCVTo3DStartUp);
var endPos = Matrix4.multiplyByPoint(Camera.TRANSFORM_2D_INVERSE, endCamera.position, scratchCVTo3DEndPos);
var endDir = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D_INVERSE, endCamera.direction, scratchCVTo3DEndDir);
var endUp = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D_INVERSE, endCamera.up, scratchCVTo3DEndUp);
function update(value) {
columbusViewMorph(startPos, endPos, value.time, camera.position);
columbusViewMorph(startDir, endDir, value.time, camera.direction);
columbusViewMorph(startUp, endUp, value.time, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
addMorphTimeAnimations(transitioner, scene, 0.0, 1.0, duration, complete);
}
});
transitioner._currentTweens.push(tween);
}
var scratch2DTo3DFrustum = new PerspectiveFrustum();
function morphFrom2DTo3D(transitioner, duration, ellipsoid) {
duration /= 3.0;
var scene = transitioner._scene;
var camera = scene.camera;
var frustum = scratch2DTo3DFrustum;
frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
frustum.fov = CesiumMath.toRadians(60.0);
var camera3D;
if (duration > 0.0) {
camera3D = scratchCVTo3DCamera;
Cartesian3.fromDegrees(0.0, 0.0, 5.0 * ellipsoid.maximumRadius, ellipsoid, camera3D.position);
Cartesian3.negate(camera3D.position, camera3D.direction);
Cartesian3.normalize(camera3D.direction, camera3D.direction);
Cartesian3.clone(Cartesian3.UNIT_Z, camera3D.up);
} else {
camera.position.z = camera.frustum.right - camera.frustum.left;
camera3D = getColumbusViewTo3DCamera(transitioner, ellipsoid);
}
camera3D.frustum = frustum;
var complete = complete3DCallback(camera3D);
createMorphHandler(transitioner, complete);
var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos);
var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir);
var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp);
var endPos = Cartesian3.fromElements(0.0, 0.0, 5.0 * ellipsoid.maximumRadius, scratch3DToCVEndPos);
var endDir = Cartesian3.negate(Cartesian3.UNIT_Z, scratch3DToCVEndDir);
var endUp = Cartesian3.clone(Cartesian3.UNIT_Y, scratch3DToCVEndUp);
var startRight = camera.frustum.right;
var endRight = endPos.z * 0.5;
function update(value) {
columbusViewMorph(startPos, endPos, value.time, camera.position);
columbusViewMorph(startDir, endDir, value.time, camera.direction);
columbusViewMorph(startUp, endUp, value.time, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
var frustum = camera.frustum;
frustum.right = CesiumMath.lerp(startRight, endRight, value.time);
frustum.left = -frustum.right;
frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
frustum.bottom = -frustum.top;
camera.position.z = 2.0 * scene.mapProjection.ellipsoid.maximumRadius;
}
if (duration > 0.0) {
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
scene._mode = SceneMode.MORPHING;
morphOrthographicToPerspective(transitioner, duration, camera3D, function() {
morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete);
});
}
});
transitioner._currentTweens.push(tween);
} else {
morphOrthographicToPerspective(transitioner, duration, camera3D, function() {
morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete);
});
}
}
function columbusViewMorph(startPosition, endPosition, time, result) {
// Just linear for now.
return Cartesian3.lerp(startPosition, endPosition, time, result);
}
function morphPerspectiveToOrthographic(transitioner, duration, endCamera, updateHeight, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
var startFOV = camera.frustum.fov;
var endFOV = CesiumMath.RADIANS_PER_DEGREE * 0.5;
var d = endCamera.position.z * Math.tan(startFOV * 0.5);
camera.frustum.far = d / Math.tan(endFOV * 0.5) + 10000000.0;
function update(value) {
camera.frustum.fov = CesiumMath.lerp(startFOV, endFOV, value.time);
var height = d / Math.tan(camera.frustum.fov * 0.5);
updateHeight(camera, height);
}
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
camera.frustum = endCamera.frustum.clone();
complete(transitioner);
}
});
transitioner._currentTweens.push(tween);
}
var scratchCVTo2DStartPos = new Cartesian3();
var scratchCVTo2DStartDir = new Cartesian3();
var scratchCVTo2DStartUp = new Cartesian3();
var scratchCVTo2DEndPos = new Cartesian3();
var scratchCVTo2DEndDir = new Cartesian3();
var scratchCVTo2DEndUp = new Cartesian3();
var scratchCVTo2DFrustum = new OrthographicFrustum();
var scratchCVTo2DRay = new Ray();
var scratchCVTo2DPickPos = new Cartesian3();
var scratchCVTo2DCamera = {
position : undefined,
direction : undefined,
up : undefined,
frustum : undefined
};
function morphFromColumbusViewTo2D(transitioner, duration) {
duration *= 0.5;
var scene = transitioner._scene;
var camera = scene.camera;
var startPos = Cartesian3.clone(camera.position, scratchCVTo2DStartPos);
var startDir = Cartesian3.clone(camera.direction, scratchCVTo2DStartDir);
var startUp = Cartesian3.clone(camera.up, scratchCVTo2DStartUp);
var endDir = Cartesian3.negate(Cartesian3.UNIT_Z, scratchCVTo2DEndDir);
var endUp = Cartesian3.clone(Cartesian3.UNIT_Y, scratchCVTo2DEndUp);
var endPos = scratchCVTo2DEndPos;
if (duration > 0.0) {
Cartesian3.clone(Cartesian3.ZERO, scratchCVTo2DEndPos);
endPos.z = 5.0 * scene.mapProjection.ellipsoid.maximumRadius;
} else {
Cartesian3.clone(startPos, scratchCVTo2DEndPos);
var ray = scratchCVTo2DRay;
Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, startPos, ray.origin);
Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, startDir, ray.direction);
var globe = scene.globe;
if (defined(globe)) {
var pickPos = globe.pick(ray, scene, scratchCVTo2DPickPos);
if (defined(pickPos)) {
Matrix4.multiplyByPoint(Camera.TRANSFORM_2D_INVERSE, pickPos, endPos);
endPos.z += Cartesian3.distance(startPos, endPos);
}
}
}
var frustum = scratchCVTo2DFrustum;
frustum.right = endPos.z * 0.5;
frustum.left = -frustum.right;
frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
frustum.bottom = -frustum.top;
var camera2D = scratchCVTo2DCamera;
camera2D.position = endPos;
camera2D.direction = endDir;
camera2D.up = endUp;
camera2D.frustum = frustum;
var complete = complete2DCallback(camera2D);
createMorphHandler(transitioner, complete);
function updateCV(value) {
columbusViewMorph(startPos, endPos, value.time, camera.position);
columbusViewMorph(startDir, endDir, value.time, camera.direction);
columbusViewMorph(startUp, endUp, value.time, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
function updateHeight(camera, height) {
camera.position.z = height;
}
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : updateCV,
complete : function() {
morphPerspectiveToOrthographic(transitioner, duration, camera2D, updateHeight, complete);
}
});
transitioner._currentTweens.push(tween);
}
var scratch3DTo2DCartographic = new Cartographic();
var scratch3DTo2DCamera = {
position : new Cartesian3(),
direction : new Cartesian3(),
up : new Cartesian3(),
position2D : new Cartesian3(),
direction2D : new Cartesian3(),
up2D : new Cartesian3(),
frustum : new OrthographicFrustum()
};
var scratch3DTo2DEndCamera = {
position : new Cartesian3(),
direction : new Cartesian3(),
up : new Cartesian3(),
frustum : undefined
};
var scratch3DTo2DPickPosition = new Cartesian3();
var scratch3DTo2DRay = new Ray();
var scratch3DTo2DToENU = new Matrix4();
var scratch3DTo2DSurfacePoint = new Cartesian3();
function morphFrom3DTo2D(transitioner, duration, ellipsoid) {
duration *= 0.5;
var scene = transitioner._scene;
var camera = scene.camera;
var camera2D = scratch3DTo2DCamera;
if (duration > 0.0) {
Cartesian3.clone(Cartesian3.ZERO, camera2D.position);
camera2D.position.z = 5.0 * ellipsoid.maximumRadius;
Cartesian3.negate(Cartesian3.UNIT_Z, camera2D.direction);
Cartesian3.clone(Cartesian3.UNIT_Y, camera2D.up);
} else {
ellipsoid.cartesianToCartographic(camera.positionWC, scratch3DTo2DCartographic);
scene.mapProjection.project(scratch3DTo2DCartographic, camera2D.position);
Cartesian3.negate(Cartesian3.UNIT_Z, camera2D.direction);
Cartesian3.clone(Cartesian3.UNIT_Y, camera2D.up);
var ray = scratch3DTo2DRay;
Cartesian3.clone(camera2D.position2D, ray.origin);
var rayDirection = Cartesian3.clone(camera.directionWC, ray.direction);
var surfacePoint = ellipsoid.scaleToGeodeticSurface(camera.positionWC, scratch3DTo2DSurfacePoint);
var toENU = Transforms.eastNorthUpToFixedFrame(surfacePoint, ellipsoid, scratch3DTo2DToENU);
Matrix4.inverseTransformation(toENU, toENU);
Matrix4.multiplyByPointAsVector(toENU, rayDirection, rayDirection);
Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, rayDirection, rayDirection);
var globe = scene.globe;
if (defined(globe)) {
var pickedPos = globe.pick(ray, scene, scratch3DTo2DPickPosition);
if (defined(pickedPos)) {
var height = Cartesian3.distance(camera2D.position2D, pickedPos);
pickedPos.x += height;
Cartesian3.clone(pickedPos, camera2D.position2D);
}
}
}
function updateHeight(camera, height) {
camera.position.x = height;
}
Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, camera2D.position, camera2D.position2D);
Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, camera2D.direction, camera2D.direction2D);
Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, camera2D.up, camera2D.up2D);
var frustum = camera2D.frustum;
frustum.right = camera2D.position.z * 0.5;
frustum.left = -frustum.right;
frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
frustum.bottom = -frustum.top;
var endCamera = scratch3DTo2DEndCamera;
Matrix4.multiplyByPoint(Camera.TRANSFORM_2D_INVERSE, camera2D.position2D, endCamera.position);
Cartesian3.clone(camera2D.direction, endCamera.direction);
Cartesian3.clone(camera2D.up, endCamera.up);
endCamera.frustum = frustum;
var complete = complete2DCallback(endCamera);
createMorphHandler(transitioner, complete);
function completeCallback() {
morphPerspectiveToOrthographic(transitioner, duration, camera2D, updateHeight, complete);
}
morphFrom3DToColumbusView(transitioner, duration, camera2D, completeCallback);
}
function morphOrthographicToPerspective(transitioner, duration, cameraCV, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
var height = camera.frustum.right - camera.frustum.left;
camera.frustum = cameraCV.frustum.clone();
var endFOV = camera.frustum.fov;
var startFOV = CesiumMath.RADIANS_PER_DEGREE * 0.5;
var d = height * Math.tan(endFOV * 0.5);
camera.frustum.far = d / Math.tan(startFOV * 0.5) + 10000000.0;
camera.frustum.fov = startFOV;
function update(value) {
camera.frustum.fov = CesiumMath.lerp(startFOV, endFOV, value.time);
camera.position.z = d / Math.tan(camera.frustum.fov * 0.5);
}
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
complete(transitioner);
}
});
transitioner._currentTweens.push(tween);
}
function morphFrom2DToColumbusView(transitioner, duration, cameraCV, complete) {
duration *= 0.5;
var scene = transitioner._scene;
var camera = scene.camera;
var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos);
var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir);
var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp);
var endPos = Cartesian3.clone(cameraCV.position, scratch3DToCVEndPos);
var endDir = Cartesian3.clone(cameraCV.direction, scratch3DToCVEndDir);
var endUp = Cartesian3.clone(cameraCV.up, scratch3DToCVEndUp);
var startRight = camera.frustum.right;
var endRight = endPos.z * 0.5;
function update(value) {
columbusViewMorph(startPos, endPos, value.time, camera.position);
columbusViewMorph(startDir, endDir, value.time, camera.direction);
columbusViewMorph(startUp, endUp, value.time, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
var frustum = camera.frustum;
frustum.right = CesiumMath.lerp(startRight, endRight, value.time);
frustum.left = -frustum.right;
frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
frustum.bottom = -frustum.top;
camera.position.z = 2.0 * scene.mapProjection.ellipsoid.maximumRadius;
}
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
scene._mode = SceneMode.MORPHING;
morphOrthographicToPerspective(transitioner, duration, cameraCV, complete);
}
});
transitioner._currentTweens.push(tween);
}
var scratch3DToCVStartPos = new Cartesian3();
var scratch3DToCVStartDir = new Cartesian3();
var scratch3DToCVStartUp = new Cartesian3();
var scratch3DToCVEndPos = new Cartesian3();
var scratch3DToCVEndDir = new Cartesian3();
var scratch3DToCVEndUp = new Cartesian3();
function morphFrom3DToColumbusView(transitioner, duration, endCamera, complete) {
var scene = transitioner._scene;
var camera = scene.camera;
var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos);
var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir);
var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp);
var endPos = Cartesian3.clone(endCamera.position2D, scratch3DToCVEndPos);
var endDir = Cartesian3.clone(endCamera.direction2D, scratch3DToCVEndDir);
var endUp = Cartesian3.clone(endCamera.up2D, scratch3DToCVEndUp);
function update(value) {
columbusViewMorph(startPos, endPos, value.time, camera.position);
columbusViewMorph(startDir, endDir, value.time, camera.direction);
columbusViewMorph(startUp, endUp, value.time, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
var tween = scene.tweens.add({
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT,
startObject : {
time : 0.0
},
stopObject : {
time : 1.0
},
update : update,
complete : function() {
addMorphTimeAnimations(transitioner, scene, 1.0, 0.0, duration, complete);
}
});
transitioner._currentTweens.push(tween);
}
function addMorphTimeAnimations(transitioner, scene, start, stop, duration, complete) {
// Later, this will be linear and each object will adjust, if desired, in its vertex shader.
var options = {
object : scene,
property : 'morphTime',
startValue : start,
stopValue : stop,
duration : duration,
easingFunction : EasingFunction.QUARTIC_OUT
};
if (defined(complete)) {
options.complete = function() {
complete(transitioner);
};
}
var tween = scene.tweens.addProperty(options);
transitioner._currentTweens.push(tween);
}
function complete3DCallback(camera3D) {
return function(transitioner) {
var scene = transitioner._scene;
scene._mode = SceneMode.SCENE3D;
scene.morphTime = SceneMode.getMorphTime(SceneMode.SCENE3D);
destroyMorphHandler(transitioner);
if (transitioner._previousMode !== SceneMode.MORPHING || transitioner._morphCancelled) {
transitioner._morphCancelled = false;
var camera = scene.camera;
Cartesian3.clone(camera3D.position, camera.position);
Cartesian3.clone(camera3D.direction, camera.direction);
Cartesian3.clone(camera3D.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
if (defined(camera3D.frustum)) {
camera.frustum = camera3D.frustum.clone();
}
}
var wasMorphing = defined(transitioner._completeMorph);
transitioner._completeMorph = undefined;
scene.camera.update(scene.mode);
transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.SCENE3D, wasMorphing);
};
}
function complete2DCallback(camera2D) {
return function(transitioner) {
var scene = transitioner._scene;
scene._mode = SceneMode.SCENE2D;
scene.morphTime = SceneMode.getMorphTime(SceneMode.SCENE2D);
destroyMorphHandler(transitioner);
var camera = scene.camera;
Cartesian3.clone(camera2D.position, camera.position);
camera.position.z = scene.mapProjection.ellipsoid.maximumRadius * 2.0;
Cartesian3.clone(camera2D.direction, camera.direction);
Cartesian3.clone(camera2D.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
camera.frustum = camera2D.frustum.clone();
var wasMorphing = defined(transitioner._completeMorph);
transitioner._completeMorph = undefined;
scene.camera.update(scene.mode);
transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.SCENE2D, wasMorphing);
};
}
function completeColumbusViewCallback(cameraCV) {
return function(transitioner) {
var scene = transitioner._scene;
scene._mode = SceneMode.COLUMBUS_VIEW;
scene.morphTime = SceneMode.getMorphTime(SceneMode.COLUMBUS_VIEW);
destroyMorphHandler(transitioner);
scene.camera.frustum = cameraCV.frustum.clone();
if (transitioner._previousModeMode !== SceneMode.MORPHING || transitioner._morphCancelled) {
transitioner._morphCancelled = false;
var camera = scene.camera;
Cartesian3.clone(cameraCV.position, camera.position);
Cartesian3.clone(cameraCV.direction, camera.direction);
Cartesian3.clone(cameraCV.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.right, camera.right);
}
var wasMorphing = defined(transitioner._completeMorph);
transitioner._completeMorph = undefined;
scene.camera.update(scene.mode);
transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.COLUMBUS_VIEW, wasMorphing);
};
}
return SceneTransitioner;
});
/*global define*/
define('Scene/TweenCollection',[
'../Core/clone',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/EasingFunction',
'../Core/getTimestamp',
'../Core/TimeConstants',
'../ThirdParty/Tween'
], function(
clone,
defaultValue,
defined,
defineProperties,
DeveloperError,
EasingFunction,
getTimestamp,
TimeConstants,
TweenJS) {
'use strict';
/**
* A tween is an animation that interpolates the properties of two objects using an {@link EasingFunction}. Create
* one using {@link Scene#tweens} and {@link TweenCollection#add} and related add functions.
*
* @alias Tween
* @constructor
*
* @private
*/
function Tween(tweens, tweenjs, startObject, stopObject, duration, delay, easingFunction, update, complete, cancel) {
this._tweens = tweens;
this._tweenjs = tweenjs;
this._startObject = clone(startObject);
this._stopObject = clone(stopObject);
this._duration = duration;
this._delay = delay;
this._easingFunction = easingFunction;
this._update = update;
this._complete = complete;
/**
* The callback to call if the tween is canceled either because {@link Tween#cancelTween}
* was called or because the tween was removed from the collection.
*
* @type {TweenCollection~TweenCancelledCallback}
*/
this.cancel = cancel;
/**
* @private
*/
this.needsStart = true;
}
defineProperties(Tween.prototype, {
/**
* An object with properties for initial values of the tween. The properties of this object are changed during the tween's animation.
* @memberof Tween.prototype
*
* @type {Object}
* @readonly
*/
startObject : {
get : function() {
return this._startObject;
}
},
/**
* An object with properties for the final values of the tween.
* @memberof Tween.prototype
*
* @type {Object}
* @readonly
*/
stopObject : {
get : function() {
return this._stopObject;
}
},
/**
* The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops.
* @memberof Tween.prototype
*
* @type {Number}
* @readonly
*/
duration : {
get : function() {
return this._duration;
}
},
/**
* The delay, in seconds, before the tween starts animating.
* @memberof Tween.prototype
*
* @type {Number}
* @readonly
*/
delay : {
get : function() {
return this._delay;
}
},
/**
* Determines the curve for animtion.
* @memberof Tween.prototype
*
* @type {EasingFunction}
* @readonly
*/
easingFunction : {
get : function() {
return this._easingFunction;
}
},
/**
* The callback to call at each animation update (usually tied to the a rendered frame).
* @memberof Tween.prototype
*
* @type {TweenCollection~TweenUpdateCallback}
* @readonly
*/
update : {
get : function() {
return this._update;
}
},
/**
* The callback to call when the tween finishes animating.
* @memberof Tween.prototype
*
* @type {TweenCollection~TweenCompleteCallback}
* @readonly
*/
complete : {
get : function() {
return this._complete;
}
},
/**
* @memberof Tween.prototype
*
* @private
*/
tweenjs : {
get : function() {
return this._tweenjs;
}
}
});
/**
* Cancels the tween calling the {@link Tween#cancel} callback if one exists. This
* has no effect if the tween finished or was already canceled.
*/
Tween.prototype.cancelTween = function() {
this._tweens.remove(this);
};
/**
* A collection of tweens for animating properties. Commonly accessed using {@link Scene#tweens}.
*
* @alias TweenCollection
* @constructor
*
* @private
*/
function TweenCollection() {
this._tweens = [];
}
defineProperties(TweenCollection.prototype, {
/**
* The number of tweens in the collection.
* @memberof TweenCollection.prototype
*
* @type {Number}
* @readonly
*/
length : {
get : function() {
return this._tweens.length;
}
}
});
/**
* Creates a tween for animating between two sets of properties. The tween starts animating at the next call to {@link TweenCollection#update}, which
* is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
*
* @param {Object} [options] Object with the following properties:
* @param {Object} options.startObject An object with properties for initial values of the tween. The properties of this object are changed during the tween's animation.
* @param {Object} options.stopObject An object with properties for the final values of the tween.
* @param {Number} options.duration The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops.
* @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
* @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
* @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
* @param {TweenCollection~TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating.
* @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
* @returns {Tween} The tween.
*
* @exception {DeveloperError} options.duration must be positive.
*/
TweenCollection.prototype.add = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.startObject) || !defined(options.stopObject)) {
throw new DeveloperError('options.startObject and options.stopObject are required.');
}
if (!defined(options.duration) || options.duration < 0.0) {
throw new DeveloperError('options.duration is required and must be positive.');
}
if (options.duration === 0.0) {
if (defined(options.complete)) {
options.complete();
}
return new Tween(this);
}
var duration = options.duration / TimeConstants.SECONDS_PER_MILLISECOND;
var delayInSeconds = defaultValue(options.delay, 0.0);
var delay = delayInSeconds / TimeConstants.SECONDS_PER_MILLISECOND;
var easingFunction = defaultValue(options.easingFunction, EasingFunction.LINEAR_NONE);
var value = options.startObject;
var tweenjs = new TweenJS.Tween(value);
tweenjs.to(clone(options.stopObject), duration);
tweenjs.delay(delay);
tweenjs.easing(easingFunction);
if (defined(options.update)) {
tweenjs.onUpdate(function() {
options.update(value);
});
}
tweenjs.onComplete(defaultValue(options.complete, null));
tweenjs.repeat(defaultValue(options._repeat, 0.0));
var tween = new Tween(this, tweenjs, options.startObject, options.stopObject, options.duration, delayInSeconds, easingFunction, options.update, options.complete, options.cancel);
this._tweens.push(tween);
return tween;
};
/**
* Creates a tween for animating a scalar property on the given object. The tween starts animating at the next call to {@link TweenCollection#update}, which
* is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
*
* @param {Object} [options] Object with the following properties:
* @param {Object} options.object The object containing the property to animate.
* @param {String} options.property The name of the property to animate.
* @param {Number} options.startValue The initial value.
* @param {Number} options.stopValue The final value.
* @param {Number} [options.duration=3.0] The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops.
* @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
* @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
* @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
* @param {TweenCollection~TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating.
* @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
* @returns {Tween} The tween.
*
* @exception {DeveloperError} options.object must have the specified property.
* @exception {DeveloperError} options.duration must be positive.
*/
TweenCollection.prototype.addProperty = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var object = options.object;
var property = options.property;
var startValue = options.startValue;
var stopValue = options.stopValue;
if (!defined(object) || !defined(options.property)) {
throw new DeveloperError('options.object and options.property are required.');
}
if (!defined(object[property])) {
throw new DeveloperError('options.object must have the specified property.');
}
if (!defined(startValue) || !defined(stopValue)) {
throw new DeveloperError('options.startValue and options.stopValue are required.');
}
function update(value) {
object[property] = value.value;
}
return this.add({
startObject : {
value : startValue
},
stopObject : {
value : stopValue
},
duration : defaultValue(options.duration, 3.0),
delay : options.delay,
easingFunction : options.easingFunction,
update : update,
complete : options.complete,
cancel : options.cancel,
_repeat : options._repeat
});
};
/**
* Creates a tween for animating the alpha of all color uniforms on a {@link Material}. The tween starts animating at the next call to {@link TweenCollection#update}, which
* is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
*
* @param {Object} [options] Object with the following properties:
* @param {Material} options.material The material to animate.
* @param {Number} [options.startValue=0.0] The initial alpha value.
* @param {Number} [options.stopValue=1.0] The final alpha value.
* @param {Number} [options.duration=3.0] The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops.
* @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
* @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
* @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
* @param {TweenCollection~TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating.
* @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
* @returns {Tween} The tween.
*
* @exception {DeveloperError} material has no properties with alpha components.
* @exception {DeveloperError} options.duration must be positive.
*/
TweenCollection.prototype.addAlpha = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var material = options.material;
if (!defined(material)) {
throw new DeveloperError('options.material is required.');
}
var properties = [];
for (var property in material.uniforms) {
if (material.uniforms.hasOwnProperty(property) &&
defined(material.uniforms[property]) &&
defined(material.uniforms[property].alpha)) {
properties.push(property);
}
}
if (properties.length === 0) {
throw new DeveloperError('material has no properties with alpha components.');
}
function update(value) {
var length = properties.length;
for (var i = 0; i < length; ++i) {
material.uniforms[properties[i]].alpha = value.alpha;
}
}
return this.add({
startObject : {
alpha : defaultValue(options.startValue, 0.0) // Default to fade in
},
stopObject : {
alpha : defaultValue(options.stopValue, 1.0)
},
duration : defaultValue(options.duration, 3.0),
delay : options.delay,
easingFunction : options.easingFunction,
update : update,
complete : options.complete,
cancel : options.cancel
});
};
/**
* Creates a tween for animating the offset uniform of a {@link Material}. The tween starts animating at the next call to {@link TweenCollection#update}, which
* is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
*
* @param {Object} [options] Object with the following properties:
* @param {Material} options.material The material to animate.
* @param {Number} options.startValue The initial alpha value.
* @param {Number} options.stopValue The final alpha value.
* @param {Number} [options.duration=3.0] The duration, in seconds, for the tween. The tween is automatically removed from the collection when it stops.
* @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
* @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
* @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
* @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
* @returns {Tween} The tween.
*
* @exception {DeveloperError} material.uniforms must have an offset property.
* @exception {DeveloperError} options.duration must be positive.
*/
TweenCollection.prototype.addOffsetIncrement = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var material = options.material;
if (!defined(material)) {
throw new DeveloperError('material is required.');
}
if (!defined(material.uniforms.offset)) {
throw new DeveloperError('material.uniforms must have an offset property.');
}
var uniforms = material.uniforms;
return this.addProperty({
object : uniforms,
property : 'offset',
startValue : uniforms.offset,
stopValue : uniforms.offset + 1,
duration : options.duration,
delay : options.delay,
easingFunction : options.easingFunction,
update : options.update,
cancel : options.cancel,
_repeat : Infinity
});
};
/**
* Removes a tween from the collection.
*
* This calls the {@link Tween#cancel} callback if the tween has one.
*
*
* @param {Tween} tween The tween to remove.
* @returns {Boolean} true
if the tween was removed; false
if the tween was not found in the collection.
*/
TweenCollection.prototype.remove = function(tween) {
if (!defined(tween)) {
return false;
}
var index = this._tweens.indexOf(tween);
if (index !== -1) {
tween.tweenjs.stop();
if (defined(tween.cancel)) {
tween.cancel();
}
this._tweens.splice(index, 1);
return true;
}
return false;
};
/**
* Removes all tweens from the collection.
*
* This calls the {@link Tween#cancel} callback for each tween that has one.
*
*/
TweenCollection.prototype.removeAll = function() {
var tweens = this._tweens;
for (var i = 0; i < tweens.length; ++i) {
var tween = tweens[i];
tween.tweenjs.stop();
if (defined(tween.cancel)) {
tween.cancel();
}
}
tweens.length = 0;
};
/**
* Determines whether this collection contains a given tween.
*
* @param {Tween} tween The tween to check for.
* @returns {Boolean} true
if this collection contains the tween, false
otherwise.
*/
TweenCollection.prototype.contains = function(tween) {
return defined(tween) && (this._tweens.indexOf(tween) !== -1);
};
/**
* Returns the tween in the collection at the specified index. Indices are zero-based
* and increase as tweens are added. Removing a tween shifts all tweens after
* it to the left, changing their indices. This function is commonly used to iterate over
* all the tween in the collection.
*
* @param {Number} index The zero-based index of the tween.
* @returns {Tween} The tween at the specified index.
*
* @example
* // Output the duration of all the tweens in the collection.
* var tweens = scene.tweens;
* var length = tweens.length;
* for (var i = 0; i < length; ++i) {
* console.log(tweens.get(i).duration);
* }
*/
TweenCollection.prototype.get = function(index) {
if (!defined(index)) {
throw new DeveloperError('index is required.');
}
return this._tweens[index];
};
/**
* Updates the tweens in the collection to be at the provide time. When a tween finishes, it is removed
* from the collection.
*
* @param {Number} [time=getTimestamp()] The time in seconds. By default tweens are synced to the system clock.
*/
TweenCollection.prototype.update = function(time) {
var tweens = this._tweens;
var i = 0;
time = defined(time) ? time / TimeConstants.SECONDS_PER_MILLISECOND : getTimestamp();
while (i < tweens.length) {
var tween = tweens[i];
var tweenjs = tween.tweenjs;
if (tween.needsStart) {
tween.needsStart = false;
tweenjs.start(time);
} else {
if (tweenjs.update(time)) {
i++;
} else {
tweenjs.stop();
tweens.splice(i, 1);
}
}
}
};
/**
* A function that will execute when a tween completes.
* @callback TweenCollection~TweenCompleteCallback
*/
/**
* A function that will execute when a tween updates.
* @callback TweenCollection~TweenUpdateCallback
*/
/**
* A function that will execute when a tween is cancelled.
* @callback TweenCollection~TweenCancelledCallback
*/
return TweenCollection;
});
/*global define*/
define('Scene/ScreenSpaceCameraController',[
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Ellipsoid',
'../Core/IntersectionTests',
'../Core/isArray',
'../Core/KeyboardEventModifier',
'../Core/Math',
'../Core/Matrix3',
'../Core/Matrix4',
'../Core/Plane',
'../Core/Quaternion',
'../Core/Ray',
'../Core/Transforms',
'./CameraEventAggregator',
'./CameraEventType',
'./MapMode2D',
'./SceneMode',
'./SceneTransforms',
'./TweenCollection'
], function(
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
defaultValue,
defined,
destroyObject,
DeveloperError,
Ellipsoid,
IntersectionTests,
isArray,
KeyboardEventModifier,
CesiumMath,
Matrix3,
Matrix4,
Plane,
Quaternion,
Ray,
Transforms,
CameraEventAggregator,
CameraEventType,
MapMode2D,
SceneMode,
SceneTransforms,
TweenCollection) {
'use strict';
/**
* Modifies the camera position and orientation based on mouse input to a canvas.
* @alias ScreenSpaceCameraController
* @constructor
*
* @param {Scene} scene The scene.
*/
function ScreenSpaceCameraController(scene) {
if (!defined(scene)) {
throw new DeveloperError('scene is required.');
}
/**
* If true, inputs are allowed conditionally with the flags enableTranslate, enableZoom,
* enableRotate, enableTilt, and enableLook. If false, all inputs are disabled.
*
* NOTE: This setting is for temporary use cases, such as camera flights and
* drag-selection of regions (see Picking demo). It is typically set to false at the
* start of such events, and set true on completion. To keep inputs disabled
* past the end of camera flights, you must use the other booleans (enableTranslate,
* enableZoom, enableRotate, enableTilt, and enableLook).
* @type {Boolean}
* @default true
*/
this.enableInputs = true;
/**
* If true, allows the user to pan around the map. If false, the camera stays locked at the current position.
* This flag only applies in 2D and Columbus view modes.
* @type {Boolean}
* @default true
*/
this.enableTranslate = true;
/**
* If true, allows the user to zoom in and out. If false, the camera is locked to the current distance from the ellipsoid.
* @type {Boolean}
* @default true
*/
this.enableZoom = true;
/**
* If true, allows the user to rotate the camera. If false, the camera is locked to the current heading.
* This flag only applies in 2D and 3D.
* @type {Boolean}
* @default true
*/
this.enableRotate = true;
/**
* If true, allows the user to tilt the camera. If false, the camera is locked to the current heading.
* This flag only applies in 3D and Columbus view.
* @type {Boolean}
* @default true
*/
this.enableTilt = true;
/**
* If true, allows the user to use free-look. If false, the camera view direction can only be changed through translating
* or rotating. This flag only applies in 3D and Columbus view modes.
* @type {Boolean}
* @default true
*/
this.enableLook = true;
/**
* A parameter in the range [0, 1)
used to determine how long
* the camera will continue to spin because of inertia.
* With value of zero, the camera will have no inertia.
* @type {Number}
* @default 0.9
*/
this.inertiaSpin = 0.9;
/**
* A parameter in the range [0, 1)
used to determine how long
* the camera will continue to translate because of inertia.
* With value of zero, the camera will have no inertia.
* @type {Number}
* @default 0.9
*/
this.inertiaTranslate = 0.9;
/**
* A parameter in the range [0, 1)
used to determine how long
* the camera will continue to zoom because of inertia.
* With value of zero, the camera will have no inertia.
* @type {Number}
* @default 0.8
*/
this.inertiaZoom = 0.8;
/**
* A parameter in the range [0, 1)
used to limit the range
* of various user inputs to a percentage of the window width/height per animation frame.
* This helps keep the camera under control in low-frame-rate situations.
* @type {Number}
* @default 0.1
*/
this.maximumMovementRatio = 0.1;
/**
* Sets the duration, in seconds, of the bounce back animations in 2D and Columbus view.
* @type {Number}
* @default 3.0
*/
this.bounceAnimationTime = 3.0;
/**
* The minimum magnitude, in meters, of the camera position when zooming. Defaults to 1.0.
* @type {Number}
* @default 1.0
*/
this.minimumZoomDistance = 1.0;
/**
* The maximum magnitude, in meters, of the camera position when zooming. Defaults to positive infinity.
* @type {Number}
* @default {@link Number.POSITIVE_INFINITY}
*/
this.maximumZoomDistance = Number.POSITIVE_INFINITY;
/**
* The input that allows the user to pan around the map. This only applies in 2D and Columbus view modes.
*
* The type came be a {@link CameraEventType}, undefined
, an object with eventType
* and modifier
properties with types CameraEventType
and {@link KeyboardEventModifier},
* or an array of any of the preceding.
*
* @type {CameraEventType|Array|undefined}
* @default {@link CameraEventType.LEFT_DRAG}
*/
this.translateEventTypes = CameraEventType.LEFT_DRAG;
/**
* The input that allows the user to zoom in/out.
*
* The type came be a {@link CameraEventType}, undefined
, an object with eventType
* and modifier
properties with types CameraEventType
and {@link KeyboardEventModifier},
* or an array of any of the preceding.
*
* @type {CameraEventType|Array|undefined}
* @default [{@link CameraEventType.RIGHT_DRAG}, {@link CameraEventType.WHEEL}, {@link CameraEventType.PINCH}]
*/
this.zoomEventTypes = [CameraEventType.RIGHT_DRAG, CameraEventType.WHEEL, CameraEventType.PINCH];
/**
* The input that allows the user to rotate around the globe or another object. This only applies in 3D and Columbus view modes.
*
* The type came be a {@link CameraEventType}, undefined
, an object with eventType
* and modifier
properties with types CameraEventType
and {@link KeyboardEventModifier},
* or an array of any of the preceding.
*
* @type {CameraEventType|Array|undefined}
* @default {@link CameraEventType.LEFT_DRAG}
*/
this.rotateEventTypes = CameraEventType.LEFT_DRAG;
/**
* The input that allows the user to tilt in 3D and Columbus view or twist in 2D.
*
* The type came be a {@link CameraEventType}, undefined
, an object with eventType
* and modifier
properties with types CameraEventType
and {@link KeyboardEventModifier},
* or an array of any of the preceding.
*
* @type {CameraEventType|Array|undefined}
* @default [{@link CameraEventType.MIDDLE_DRAG}, {@link CameraEventType.PINCH}, {
* eventType : {@link CameraEventType.LEFT_DRAG},
* modifier : {@link KeyboardEventModifier.CTRL}
* }, {
* eventType : {@link CameraEventType.RIGHT_DRAG},
* modifier : {@link KeyboardEventModifier.CTRL}
* }]
*/
this.tiltEventTypes = [CameraEventType.MIDDLE_DRAG, CameraEventType.PINCH, {
eventType : CameraEventType.LEFT_DRAG,
modifier : KeyboardEventModifier.CTRL
}, {
eventType : CameraEventType.RIGHT_DRAG,
modifier : KeyboardEventModifier.CTRL
}];
/**
* The input that allows the user to change the direction the camera is viewing. This only applies in 3D and Columbus view modes.
*
* The type came be a {@link CameraEventType}, undefined
, an object with eventType
* and modifier
properties with types CameraEventType
and {@link KeyboardEventModifier},
* or an array of any of the preceding.
*
* @type {CameraEventType|Array|undefined}
* @default { eventType : {@link CameraEventType.LEFT_DRAG}, modifier : {@link KeyboardEventModifier.SHIFT} }
*/
this.lookEventTypes = {
eventType : CameraEventType.LEFT_DRAG,
modifier : KeyboardEventModifier.SHIFT
};
/**
* The minimum height the camera must be before picking the terrain instead of the ellipsoid.
* @type {Number}
* @default 150000.0
*/
this.minimumPickingTerrainHeight = 150000.0;
this._minimumPickingTerrainHeight = this.minimumPickingTerrainHeight;
/**
* The minimum height the camera must be before testing for collision with terrain.
* @type {Number}
* @default 10000.0
*/
this.minimumCollisionTerrainHeight = 15000.0;
this._minimumCollisionTerrainHeight = this.minimumCollisionTerrainHeight;
/**
* The minimum height the camera must be before switching from rotating a track ball to
* free look when clicks originate on the sky on in space.
* @type {Number}
* @default 7500000.0
*/
this.minimumTrackBallHeight = 7500000.0;
this._minimumTrackBallHeight = this.minimumTrackBallHeight;
/**
* Enables or disables camera collision detection with terrain.
* @type {Boolean}
* @default true
*/
this.enableCollisionDetection = true;
this._scene = scene;
this._globe = undefined;
this._ellipsoid = undefined;
this._aggregator = new CameraEventAggregator(scene.canvas);
this._lastInertiaSpinMovement = undefined;
this._lastInertiaZoomMovement = undefined;
this._lastInertiaTranslateMovement = undefined;
this._lastInertiaTiltMovement = undefined;
this._tweens = new TweenCollection();
this._tween = undefined;
this._horizontalRotationAxis = undefined;
this._tiltCenterMousePosition = new Cartesian2(-1.0, -1.0);
this._tiltCenter = new Cartesian3();
this._rotateMousePosition = new Cartesian2(-1.0, -1.0);
this._rotateStartPosition = new Cartesian3();
this._strafeStartPosition = new Cartesian3();
this._zoomMouseStart = new Cartesian2(-1.0, -1.0);
this._zoomWorldPosition = new Cartesian3();
this._useZoomWorldPosition = false;
this._tiltCVOffMap = false;
this._looking = false;
this._rotating = false;
this._strafing = false;
this._zoomingOnVector = false;
this._rotatingZoom = false;
var projection = scene.mapProjection;
this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO));
// Constants, Make any of these public?
this._zoomFactor = 5.0;
this._rotateFactor = undefined;
this._rotateRateRangeAdjustment = undefined;
this._maximumRotateRate = 1.77;
this._minimumRotateRate = 1.0 / 5000.0;
this._minimumZoomRate = 20.0;
this._maximumZoomRate = 5906376272000.0; // distance from the Sun to Pluto in meters.
}
function decay(time, coefficient) {
if (time < 0) {
return 0.0;
}
var tau = (1.0 - coefficient) * 25.0;
return Math.exp(-tau * time);
}
function sameMousePosition(movement) {
return Cartesian2.equalsEpsilon(movement.startPosition, movement.endPosition, CesiumMath.EPSILON14);
}
// If the time between mouse down and mouse up is not between
// these thresholds, the camera will not move with inertia.
// This value is probably dependent on the browser and/or the
// hardware. Should be investigated further.
var inertiaMaxClickTimeThreshold = 0.4;
function maintainInertia(aggregator, type, modifier, decayCoef, action, object, lastMovementName) {
var movementState = object[lastMovementName];
if (!defined(movementState)) {
movementState = object[lastMovementName] = {
startPosition : new Cartesian2(),
endPosition : new Cartesian2(),
motion : new Cartesian2(),
active : false
};
}
var ts = aggregator.getButtonPressTime(type, modifier);
var tr = aggregator.getButtonReleaseTime(type, modifier);
var threshold = ts && tr && ((tr.getTime() - ts.getTime()) / 1000.0);
var now = new Date();
var fromNow = tr && ((now.getTime() - tr.getTime()) / 1000.0);
if (ts && tr && threshold < inertiaMaxClickTimeThreshold) {
var d = decay(fromNow, decayCoef);
if (!movementState.active) {
var lastMovement = aggregator.getLastMovement(type, modifier);
if (!defined(lastMovement) || sameMousePosition(lastMovement)) {
return;
}
movementState.motion.x = (lastMovement.endPosition.x - lastMovement.startPosition.x) * 0.5;
movementState.motion.y = (lastMovement.endPosition.y - lastMovement.startPosition.y) * 0.5;
movementState.startPosition = Cartesian2.clone(lastMovement.startPosition, movementState.startPosition);
movementState.endPosition = Cartesian2.multiplyByScalar(movementState.motion, d, movementState.endPosition);
movementState.endPosition = Cartesian2.add(movementState.startPosition, movementState.endPosition, movementState.endPosition);
movementState.active = true;
} else {
movementState.startPosition = Cartesian2.clone(movementState.endPosition, movementState.startPosition);
movementState.endPosition = Cartesian2.multiplyByScalar(movementState.motion, d, movementState.endPosition);
movementState.endPosition = Cartesian2.add(movementState.startPosition, movementState.endPosition, movementState.endPosition);
movementState.motion = Cartesian2.clone(Cartesian2.ZERO, movementState.motion);
}
// If value from the decreasing exponential function is close to zero,
// the end coordinates may be NaN.
if (isNaN(movementState.endPosition.x) || isNaN(movementState.endPosition.y) || Cartesian2.distance(movementState.startPosition, movementState.endPosition) < 0.5) {
movementState.active = false;
return;
}
if (!aggregator.isButtonDown(type, modifier)) {
var startPosition = aggregator.getStartMousePosition(type, modifier);
action(object, startPosition, movementState);
}
} else {
movementState.active = false;
}
}
var scratchEventTypeArray = [];
function reactToInput(controller, enabled, eventTypes, action, inertiaConstant, inertiaStateName) {
if (!defined(eventTypes)) {
return;
}
var aggregator = controller._aggregator;
if (!isArray(eventTypes)) {
scratchEventTypeArray[0] = eventTypes;
eventTypes = scratchEventTypeArray;
}
var length = eventTypes.length;
for (var i = 0; i < length; ++i) {
var eventType = eventTypes[i];
var type = defined(eventType.eventType) ? eventType.eventType : eventType;
var modifier = eventType.modifier;
var movement = aggregator.isMoving(type, modifier) && aggregator.getMovement(type, modifier);
var startPosition = aggregator.getStartMousePosition(type, modifier);
if (controller.enableInputs && enabled) {
if (movement) {
action(controller, startPosition, movement);
} else if (inertiaConstant < 1.0) {
maintainInertia(aggregator, type, modifier, inertiaConstant, action, controller, inertiaStateName);
}
}
}
}
var scratchZoomPickRay = new Ray();
var scratchPickCartesian = new Cartesian3();
var scratchZoomOffset = new Cartesian2();
var scratchZoomDirection = new Cartesian3();
var scratchCenterPixel = new Cartesian2();
var scratchCenterPosition = new Cartesian3();
var scratchPositionNormal = new Cartesian3();
var scratchPickNormal = new Cartesian3();
var scratchZoomAxis = new Cartesian3();
var scratchCameraPositionNormal = new Cartesian3();
// Scratch variables used in zooming algorithm
var scratchTargetNormal = new Cartesian3();
var scratchCameraPosition = new Cartesian3();
var scratchCameraUpNormal = new Cartesian3();
var scratchCameraRightNormal = new Cartesian3();
var scratchForwardNormal = new Cartesian3();
var scratchPositionToTarget = new Cartesian3();
var scratchPositionToTargetNormal = new Cartesian3();
var scratchPan = new Cartesian3();
var scratchCenterMovement = new Cartesian3();
var scratchCenter = new Cartesian3();
var scratchCartesian = new Cartesian3();
var scratchCartesianTwo = new Cartesian3();
var scratchCartesianThree = new Cartesian3();
function handleZoom(object, startPosition, movement, zoomFactor, distanceMeasure, unitPositionDotDirection) {
var percentage = 1.0;
if (defined(unitPositionDotDirection)) {
percentage = CesiumMath.clamp(Math.abs(unitPositionDotDirection), 0.25, 1.0);
}
// distanceMeasure should be the height above the ellipsoid.
// The zoomRate slows as it approaches the surface and stops minimumZoomDistance above it.
var minHeight = object.minimumZoomDistance * percentage;
var maxHeight = object.maximumZoomDistance;
var minDistance = distanceMeasure - minHeight;
var zoomRate = zoomFactor * minDistance;
zoomRate = CesiumMath.clamp(zoomRate, object._minimumZoomRate, object._maximumZoomRate);
var diff = movement.endPosition.y - movement.startPosition.y;
var rangeWindowRatio = diff / object._scene.canvas.clientHeight;
rangeWindowRatio = Math.min(rangeWindowRatio, object.maximumMovementRatio);
var distance = zoomRate * rangeWindowRatio;
if (distance > 0.0 && Math.abs(distanceMeasure - minHeight) < 1.0) {
return;
}
if (distance < 0.0 && Math.abs(distanceMeasure - maxHeight) < 1.0) {
return;
}
if (distanceMeasure - distance < minHeight) {
distance = distanceMeasure - minHeight - 1.0;
} else if (distanceMeasure - distance > maxHeight) {
distance = distanceMeasure - maxHeight;
}
var scene = object._scene;
var camera = scene.camera;
var mode = scene.mode;
var sameStartPosition = Cartesian2.equals(startPosition, object._zoomMouseStart);
var zoomingOnVector = object._zoomingOnVector;
var rotatingZoom = object._rotatingZoom;
var pickedPosition;
if (!sameStartPosition) {
object._zoomMouseStart = Cartesian2.clone(startPosition, object._zoomMouseStart);
if (defined(object._globe)) {
pickedPosition = mode !== SceneMode.SCENE2D ? pickGlobe(object, startPosition, scratchPickCartesian) : camera.getPickRay(startPosition, scratchZoomPickRay).origin;
}
if (defined(pickedPosition)) {
object._useZoomWorldPosition = true;
object._zoomWorldPosition = Cartesian3.clone(pickedPosition, object._zoomWorldPosition);
} else {
object._useZoomWorldPosition = false;
}
zoomingOnVector = object._zoomingOnVector = false;
rotatingZoom = object._rotatingZoom = false;
}
if (!object._useZoomWorldPosition) {
camera.zoomIn(distance);
return;
}
var zoomOnVector = mode === SceneMode.COLUMBUS_VIEW;
if (camera.positionCartographic.height < 2000000) {
rotatingZoom = true;
}
if (!sameStartPosition || rotatingZoom) {
if (mode === SceneMode.SCENE2D) {
var worldPosition = object._zoomWorldPosition;
var endPosition = camera.position;
if (!Cartesian3.equals(worldPosition, endPosition) && camera.positionCartographic.height < object._maxCoord.x * 2.0) {
var savedX = camera.position.x;
var direction = Cartesian3.subtract(worldPosition, endPosition, scratchZoomDirection);
Cartesian3.normalize(direction, direction);
var d = Cartesian3.distance(worldPosition, endPosition) * distance / (camera.getMagnitude() * 0.5);
camera.move(direction, d * 0.5);
if ((camera.position.x < 0.0 && savedX > 0.0) || (camera.position.x > 0.0 && savedX < 0.0)) {
pickedPosition = camera.getPickRay(startPosition, scratchZoomPickRay).origin;
object._zoomWorldPosition = Cartesian3.clone(pickedPosition, object._zoomWorldPosition);
}
}
} else if (mode === SceneMode.SCENE3D) {
var cameraPositionNormal = Cartesian3.normalize(camera.position, scratchCameraPositionNormal);
if (camera.positionCartographic.height < 3000.0 && Math.abs(Cartesian3.dot(camera.direction, cameraPositionNormal)) < 0.6) {
zoomOnVector = true;
} else {
var canvas = scene.canvas;
var centerPixel = scratchCenterPixel;
centerPixel.x = canvas.clientWidth / 2;
centerPixel.y = canvas.clientHeight / 2;
var centerPosition = pickGlobe(object, centerPixel, scratchCenterPosition);
// If centerPosition is not defined, it means the globe does not cover the center position of screen
if (defined(centerPosition) && camera.positionCartographic.height < 1000000) {
var cameraPosition = scratchCameraPosition;
Cartesian3.clone(camera.position, cameraPosition);
var target = object._zoomWorldPosition;
var targetNormal = scratchTargetNormal;
targetNormal = Cartesian3.normalize(target, targetNormal);
if (Cartesian3.dot(targetNormal, cameraPositionNormal) < 0.0) {
return;
}
var center = scratchCenter;
var forward = scratchForwardNormal;
Cartesian3.clone(camera.direction, forward);
Cartesian3.add(cameraPosition, Cartesian3.multiplyByScalar(forward, 1000, scratchCartesian), center);
var positionToTarget = scratchPositionToTarget;
var positionToTargetNormal = scratchPositionToTargetNormal;
Cartesian3.subtract(target, cameraPosition, positionToTarget);
Cartesian3.normalize(positionToTarget, positionToTargetNormal);
var alpha = Math.acos( -Cartesian3.dot( cameraPositionNormal, positionToTargetNormal ) );
var cameraDistance = Cartesian3.magnitude( cameraPosition );
var targetDistance = Cartesian3.magnitude( target );
var remainingDistance = cameraDistance - distance;
var positionToTargetDistance = Cartesian3.magnitude(positionToTarget);
var gamma = Math.asin( CesiumMath.clamp( positionToTargetDistance / targetDistance * Math.sin(alpha), -1.0, 1.0 ) );
var delta = Math.asin( CesiumMath.clamp( remainingDistance / targetDistance * Math.sin(alpha), -1.0, 1.0 ) );
var beta = gamma - delta + alpha;
var up = scratchCameraUpNormal;
Cartesian3.normalize(cameraPosition, up);
var right = scratchCameraRightNormal;
right = Cartesian3.cross(positionToTargetNormal, up, right);
right = Cartesian3.normalize(right, right );
Cartesian3.normalize( Cartesian3.cross(up, right, scratchCartesian), forward );
// Calculate new position to move to
Cartesian3.multiplyByScalar(Cartesian3.normalize(center, scratchCartesian), (Cartesian3.magnitude(center) - distance), center);
Cartesian3.normalize(cameraPosition, cameraPosition);
Cartesian3.multiplyByScalar(cameraPosition, remainingDistance, cameraPosition);
// Pan
var pMid = scratchPan;
Cartesian3.multiplyByScalar(Cartesian3.add(
Cartesian3.multiplyByScalar(up, Math.cos(beta) - 1, scratchCartesianTwo),
Cartesian3.multiplyByScalar(forward, Math.sin(beta), scratchCartesianThree),
scratchCartesian
), remainingDistance, pMid);
Cartesian3.add(cameraPosition, pMid, cameraPosition);
Cartesian3.normalize(center, up);
Cartesian3.normalize( Cartesian3.cross(up, right, scratchCartesian), forward );
var cMid = scratchCenterMovement;
Cartesian3.multiplyByScalar(Cartesian3.add(
Cartesian3.multiplyByScalar(up, Math.cos(beta) - 1, scratchCartesianTwo),
Cartesian3.multiplyByScalar(forward, Math.sin(beta), scratchCartesianThree),
scratchCartesian
), Cartesian3.magnitude(center), cMid);
Cartesian3.add(center, cMid, center);
// Update camera
// Set new position
Cartesian3.clone(cameraPosition, camera.position);
// Set new direction
Cartesian3.normalize(Cartesian3.subtract(center, cameraPosition, scratchCartesian), camera.direction);
Cartesian3.clone(camera.direction, camera.direction);
// Set new right & up vectors
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.cross(camera.right, camera.direction, camera.up);
return;
} else if (defined(centerPosition)) {
var positionNormal = Cartesian3.normalize(centerPosition, scratchPositionNormal);
var pickedNormal = Cartesian3.normalize(object._zoomWorldPosition, scratchPickNormal);
var dotProduct = Cartesian3.dot(pickedNormal, positionNormal);
if (dotProduct > 0.0 && dotProduct < 1.0) {
var angle = CesiumMath.acosClamped(dotProduct);
var axis = Cartesian3.cross(pickedNormal, positionNormal, scratchZoomAxis);
var denom = Math.abs(angle) > CesiumMath.toRadians(20.0) ? camera.positionCartographic.height * 0.75 : camera.positionCartographic.height - distance;
var scalar = distance / denom;
camera.rotate(axis, angle * scalar);
}
} else {
zoomOnVector = true;
}
}
}
object._rotatingZoom = !zoomOnVector;
}
if ((!sameStartPosition && zoomOnVector) || zoomingOnVector) {
var ray;
var zoomMouseStart = SceneTransforms.wgs84ToWindowCoordinates(scene, object._zoomWorldPosition, scratchZoomOffset);
if (mode !== SceneMode.COLUMBUS_VIEW && Cartesian2.equals(startPosition, object._zoomMouseStart) && defined(zoomMouseStart)) {
ray = camera.getPickRay(zoomMouseStart, scratchZoomPickRay);
} else {
ray = camera.getPickRay(startPosition, scratchZoomPickRay);
}
var rayDirection = ray.direction;
if (mode === SceneMode.COLUMBUS_VIEW) {
Cartesian3.fromElements(rayDirection.y, rayDirection.z, rayDirection.x, rayDirection);
}
camera.move(rayDirection, distance);
object._zoomingOnVector = true;
} else {
camera.zoomIn(distance);
}
}
var translate2DStart = new Ray();
var translate2DEnd = new Ray();
var scratchTranslateP0 = new Cartesian3();
function translate2D(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
var start = camera.getPickRay(movement.startPosition, translate2DStart).origin;
var end = camera.getPickRay(movement.endPosition, translate2DEnd).origin;
var direction = Cartesian3.subtract(start, end, scratchTranslateP0);
var distance = Cartesian3.magnitude(direction);
if (distance > 0.0) {
Cartesian3.normalize(direction, direction);
camera.move(direction, distance);
}
}
function zoom2D(controller, startPosition, movement) {
if (defined(movement.distance)) {
movement = movement.distance;
}
var scene = controller._scene;
var camera = scene.camera;
handleZoom(controller, startPosition, movement, controller._zoomFactor, camera.getMagnitude());
}
var twist2DStart = new Cartesian2();
var twist2DEnd = new Cartesian2();
function twist2D(controller, startPosition, movement) {
if (defined(movement.angleAndHeight)) {
singleAxisTwist2D(controller, startPosition, movement.angleAndHeight);
return;
}
var scene = controller._scene;
var camera = scene.camera;
var canvas = scene.canvas;
var width = canvas.clientWidth;
var height = canvas.clientHeight;
var start = twist2DStart;
start.x = (2.0 / width) * movement.startPosition.x - 1.0;
start.y = (2.0 / height) * (height - movement.startPosition.y) - 1.0;
start = Cartesian2.normalize(start, start);
var end = twist2DEnd;
end.x = (2.0 / width) * movement.endPosition.x - 1.0;
end.y = (2.0 / height) * (height - movement.endPosition.y) - 1.0;
end = Cartesian2.normalize(end, end);
var startTheta = CesiumMath.acosClamped(start.x);
if (start.y < 0) {
startTheta = CesiumMath.TWO_PI - startTheta;
}
var endTheta = CesiumMath.acosClamped(end.x);
if (end.y < 0) {
endTheta = CesiumMath.TWO_PI - endTheta;
}
var theta = endTheta - startTheta;
camera.twistRight(theta);
}
function singleAxisTwist2D(controller, startPosition, movement) {
var rotateRate = controller._rotateFactor * controller._rotateRateRangeAdjustment;
if (rotateRate > controller._maximumRotateRate) {
rotateRate = controller._maximumRotateRate;
}
if (rotateRate < controller._minimumRotateRate) {
rotateRate = controller._minimumRotateRate;
}
var scene = controller._scene;
var camera = scene.camera;
var canvas = scene.canvas;
var phiWindowRatio = (movement.endPosition.x - movement.startPosition.x) / canvas.clientWidth;
phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio);
var deltaPhi = rotateRate * phiWindowRatio * Math.PI * 4.0;
camera.twistRight(deltaPhi);
}
function update2D(controller) {
var rotatable2D = controller._scene.mapMode2D === MapMode2D.ROTATE;
if (!Matrix4.equals(Matrix4.IDENTITY, controller._scene.camera.transform)) {
reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom2D, controller.inertiaZoom, '_lastInertiaZoomMovement');
if (rotatable2D) {
reactToInput(controller, controller.enableRotate, controller.translateEventTypes, twist2D, controller.inertiaSpin, '_lastInertiaSpinMovement');
}
} else {
reactToInput(controller, controller.enableTranslate, controller.translateEventTypes, translate2D, controller.inertiaTranslate, '_lastInertiaTranslateMovement');
reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom2D, controller.inertiaZoom, '_lastInertiaZoomMovement');
if (rotatable2D) {
reactToInput(controller, controller.enableRotate, controller.tiltEventTypes, twist2D, controller.inertiaSpin, '_lastInertiaTiltMovement');
}
}
}
var pickGlobeScratchRay = new Ray();
var scratchDepthIntersection = new Cartesian3();
var scratchRayIntersection = new Cartesian3();
function pickGlobe(controller, mousePosition, result) {
var scene = controller._scene;
var globe = controller._globe;
var camera = scene.camera;
if (!defined(globe)) {
return undefined;
}
var depthIntersection;
if (scene.pickPositionSupported) {
depthIntersection = scene.pickPosition(mousePosition, scratchDepthIntersection);
}
var ray = camera.getPickRay(mousePosition, pickGlobeScratchRay);
var rayIntersection = globe.pick(ray, scene, scratchRayIntersection);
var pickDistance = defined(depthIntersection) ? Cartesian3.distance(depthIntersection, camera.positionWC) : Number.POSITIVE_INFINITY;
var rayDistance = defined(rayIntersection) ? Cartesian3.distance(rayIntersection, camera.positionWC) : Number.POSITIVE_INFINITY;
if (pickDistance < rayDistance) {
return Cartesian3.clone(depthIntersection, result);
}
return Cartesian3.clone(rayIntersection, result);
}
var translateCVStartRay = new Ray();
var translateCVEndRay = new Ray();
var translateCVStartPos = new Cartesian3();
var translateCVEndPos = new Cartesian3();
var translatCVDifference = new Cartesian3();
var translateCVOrigin = new Cartesian3();
var translateCVPlane = new Plane(Cartesian3.ZERO, 0.0);
var translateCVStartMouse = new Cartesian2();
var translateCVEndMouse = new Cartesian2();
function translateCV(controller, startPosition, movement) {
if (!Cartesian3.equals(startPosition, controller._translateMousePosition)) {
controller._looking = false;
}
if (!Cartesian3.equals(startPosition, controller._strafeMousePosition)) {
controller._strafing = false;
}
if (controller._looking) {
look3D(controller, startPosition, movement);
return;
}
if (controller._strafing) {
strafe(controller, startPosition, movement);
return;
}
var scene = controller._scene;
var camera = scene.camera;
var startMouse = Cartesian2.clone(movement.startPosition, translateCVStartMouse);
var endMouse = Cartesian2.clone(movement.endPosition, translateCVEndMouse);
var startRay = camera.getPickRay(startMouse, translateCVStartRay);
var origin = Cartesian3.clone(Cartesian3.ZERO, translateCVOrigin);
var normal = Cartesian3.UNIT_X;
var globePos;
if (camera.position.z < controller._minimumPickingTerrainHeight) {
globePos = pickGlobe(controller, startMouse, translateCVStartPos);
if (defined(globePos)) {
origin.x = globePos.x;
}
}
if (origin.x > camera.position.z && defined(globePos)) {
Cartesian3.clone(globePos, controller._strafeStartPosition);
controller._strafing = true;
strafe(controller, startPosition, movement);
controller._strafeMousePosition = Cartesian2.clone(startPosition, controller._strafeMousePosition);
return;
}
var plane = Plane.fromPointNormal(origin, normal, translateCVPlane);
startRay = camera.getPickRay(startMouse, translateCVStartRay);
var startPlanePos = IntersectionTests.rayPlane(startRay, plane, translateCVStartPos);
var endRay = camera.getPickRay(endMouse, translateCVEndRay);
var endPlanePos = IntersectionTests.rayPlane(endRay, plane, translateCVEndPos);
if (!defined(startPlanePos) || !defined(endPlanePos)) {
controller._looking = true;
look3D(controller, startPosition, movement);
Cartesian2.clone(startPosition, controller._translateMousePosition);
return;
}
var diff = Cartesian3.subtract(startPlanePos, endPlanePos, translatCVDifference);
var temp = diff.x;
diff.x = diff.y;
diff.y = diff.z;
diff.z = temp;
var mag = Cartesian3.magnitude(diff);
if (mag > CesiumMath.EPSILON6) {
Cartesian3.normalize(diff, diff);
camera.move(diff, mag);
}
}
var rotateCVWindowPos = new Cartesian2();
var rotateCVWindowRay = new Ray();
var rotateCVCenter = new Cartesian3();
var rotateCVVerticalCenter = new Cartesian3();
var rotateCVTransform = new Matrix4();
var rotateCVVerticalTransform = new Matrix4();
var rotateCVOrigin = new Cartesian3();
var rotateCVPlane = new Plane(Cartesian3.ZERO, 0.0);
var rotateCVCartesian3 = new Cartesian3();
var rotateCVCart = new Cartographic();
var rotateCVOldTransform = new Matrix4();
var rotateCVQuaternion = new Quaternion();
var rotateCVMatrix = new Matrix3();
function rotateCV(controller, startPosition, movement) {
if (defined(movement.angleAndHeight)) {
movement = movement.angleAndHeight;
}
if (!Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
controller._tiltCVOffMap = false;
controller._looking = false;
}
if (controller._looking) {
look3D(controller, startPosition, movement);
return;
}
var scene = controller._scene;
var camera = scene.camera;
var maxCoord = controller._maxCoord;
var onMap = Math.abs(camera.position.x) - maxCoord.x < 0 && Math.abs(camera.position.y) - maxCoord.y < 0;
if (controller._tiltCVOffMap || !onMap || camera.position.z > controller._minimumPickingTerrainHeight) {
controller._tiltCVOffMap = true;
rotateCVOnPlane(controller, startPosition, movement);
} else {
rotateCVOnTerrain(controller, startPosition, movement);
}
}
function rotateCVOnPlane(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
var canvas = scene.canvas;
var windowPosition = rotateCVWindowPos;
windowPosition.x = canvas.clientWidth / 2;
windowPosition.y = canvas.clientHeight / 2;
var ray = camera.getPickRay(windowPosition, rotateCVWindowRay);
var normal = Cartesian3.UNIT_X;
var position = ray.origin;
var direction = ray.direction;
var scalar;
var normalDotDirection = Cartesian3.dot(normal, direction);
if (Math.abs(normalDotDirection) > CesiumMath.EPSILON6) {
scalar = -Cartesian3.dot(normal, position) / normalDotDirection;
}
if (!defined(scalar) || scalar <= 0.0) {
controller._looking = true;
look3D(controller, startPosition, movement);
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
return;
}
var center = Cartesian3.multiplyByScalar(direction, scalar, rotateCVCenter);
Cartesian3.add(position, center, center);
var projection = scene.mapProjection;
var ellipsoid = projection.ellipsoid;
Cartesian3.fromElements(center.y, center.z, center.x, center);
var cart = projection.unproject(center, rotateCVCart);
ellipsoid.cartographicToCartesian(cart, center);
var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, rotateCVTransform);
var oldGlobe = controller._globe;
var oldEllipsoid = controller._ellipsoid;
controller._globe = undefined;
controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
controller._rotateFactor = 1.0;
controller._rotateRateRangeAdjustment = 1.0;
var oldTransform = Matrix4.clone(camera.transform, rotateCVOldTransform);
camera._setTransform(transform);
rotate3D(controller, startPosition, movement, Cartesian3.UNIT_Z);
camera._setTransform(oldTransform);
controller._globe = oldGlobe;
controller._ellipsoid = oldEllipsoid;
var radius = oldEllipsoid.maximumRadius;
controller._rotateFactor = 1.0 / radius;
controller._rotateRateRangeAdjustment = radius;
}
function rotateCVOnTerrain(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
var center;
var ray;
var normal = Cartesian3.UNIT_X;
if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
center = Cartesian3.clone(controller._tiltCenter, rotateCVCenter);
} else {
if (camera.position.z < controller._minimumPickingTerrainHeight) {
center = pickGlobe(controller, startPosition, rotateCVCenter);
}
if (!defined(center)) {
ray = camera.getPickRay(startPosition, rotateCVWindowRay);
var position = ray.origin;
var direction = ray.direction;
var scalar;
var normalDotDirection = Cartesian3.dot(normal, direction);
if (Math.abs(normalDotDirection) > CesiumMath.EPSILON6) {
scalar = -Cartesian3.dot(normal, position) / normalDotDirection;
}
if (!defined(scalar) || scalar <= 0.0) {
controller._looking = true;
look3D(controller, startPosition, movement);
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
return;
}
center = Cartesian3.multiplyByScalar(direction, scalar, rotateCVCenter);
Cartesian3.add(position, center, center);
}
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
Cartesian3.clone(center, controller._tiltCenter);
}
var canvas = scene.canvas;
var windowPosition = rotateCVWindowPos;
windowPosition.x = canvas.clientWidth / 2;
windowPosition.y = controller._tiltCenterMousePosition.y;
ray = camera.getPickRay(windowPosition, rotateCVWindowRay);
var origin = Cartesian3.clone(Cartesian3.ZERO, rotateCVOrigin);
origin.x = center.x;
var plane = Plane.fromPointNormal(origin, normal, rotateCVPlane);
var verticalCenter = IntersectionTests.rayPlane(ray, plane, rotateCVVerticalCenter);
var projection = camera._projection;
var ellipsoid = projection.ellipsoid;
Cartesian3.fromElements(center.y, center.z, center.x, center);
var cart = projection.unproject(center, rotateCVCart);
ellipsoid.cartographicToCartesian(cart, center);
var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, rotateCVTransform);
var verticalTransform;
if (defined(verticalCenter)) {
Cartesian3.fromElements(verticalCenter.y, verticalCenter.z, verticalCenter.x, verticalCenter);
cart = projection.unproject(verticalCenter, rotateCVCart);
ellipsoid.cartographicToCartesian(cart, verticalCenter);
verticalTransform = Transforms.eastNorthUpToFixedFrame(verticalCenter, ellipsoid, rotateCVVerticalTransform);
} else {
verticalTransform = transform;
}
var oldGlobe = controller._globe;
var oldEllipsoid = controller._ellipsoid;
controller._globe = undefined;
controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
controller._rotateFactor = 1.0;
controller._rotateRateRangeAdjustment = 1.0;
var constrainedAxis = Cartesian3.UNIT_Z;
var oldTransform = Matrix4.clone(camera.transform, rotateCVOldTransform);
camera._setTransform(transform);
var tangent = Cartesian3.cross(Cartesian3.UNIT_Z, Cartesian3.normalize(camera.position, rotateCVCartesian3), rotateCVCartesian3);
var dot = Cartesian3.dot(camera.right, tangent);
rotate3D(controller, startPosition, movement, constrainedAxis, false, true);
camera._setTransform(verticalTransform);
if (dot < 0.0) {
if (movement.startPosition.y > movement.endPosition.y) {
constrainedAxis = undefined;
}
var oldConstrainedAxis = camera.constrainedAxis;
camera.constrainedAxis = undefined;
rotate3D(controller, startPosition, movement, constrainedAxis, true, false);
camera.constrainedAxis = oldConstrainedAxis;
} else {
rotate3D(controller, startPosition, movement, constrainedAxis, true, false);
}
if (defined(camera.constrainedAxis)) {
var right = Cartesian3.cross(camera.direction, camera.constrainedAxis, tilt3DCartesian3);
if (!Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
if (Cartesian3.dot(right, camera.right) < 0.0) {
Cartesian3.negate(right, right);
}
Cartesian3.cross(right, camera.direction, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.up, camera.up);
Cartesian3.normalize(camera.right, camera.right);
}
}
camera._setTransform(oldTransform);
controller._globe = oldGlobe;
controller._ellipsoid = oldEllipsoid;
var radius = oldEllipsoid.maximumRadius;
controller._rotateFactor = 1.0 / radius;
controller._rotateRateRangeAdjustment = radius;
var originalPosition = Cartesian3.clone(camera.positionWC, rotateCVCartesian3);
camera._adjustHeightForTerrain();
if (!Cartesian3.equals(camera.positionWC, originalPosition)) {
camera._setTransform(verticalTransform);
camera.worldToCameraCoordinatesPoint(originalPosition, originalPosition);
var magSqrd = Cartesian3.magnitudeSquared(originalPosition);
if (Cartesian3.magnitudeSquared(camera.position) > magSqrd) {
Cartesian3.normalize(camera.position, camera.position);
Cartesian3.multiplyByScalar(camera.position, Math.sqrt(magSqrd), camera.position);
}
var angle = Cartesian3.angleBetween(originalPosition, camera.position);
var axis = Cartesian3.cross(originalPosition, camera.position, originalPosition);
Cartesian3.normalize(axis, axis);
var quaternion = Quaternion.fromAxisAngle(axis, angle, rotateCVQuaternion);
var rotation = Matrix3.fromQuaternion(quaternion, rotateCVMatrix);
Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
Matrix3.multiplyByVector(rotation, camera.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.cross(camera.right, camera.direction, camera.up);
camera._setTransform(oldTransform);
}
}
var zoomCVWindowPos = new Cartesian2();
var zoomCVWindowRay = new Ray();
var zoomCVIntersection = new Cartesian3();
function zoomCV(controller, startPosition, movement) {
if (defined(movement.distance)) {
movement = movement.distance;
}
var scene = controller._scene;
var camera = scene.camera;
var canvas = scene.canvas;
var windowPosition = zoomCVWindowPos;
windowPosition.x = canvas.clientWidth / 2;
windowPosition.y = canvas.clientHeight / 2;
var ray = camera.getPickRay(windowPosition, zoomCVWindowRay);
var intersection;
if (camera.position.z < controller._minimumPickingTerrainHeight) {
intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);
}
var distance;
if (defined(intersection)) {
distance = Cartesian3.distance(ray.origin, intersection);
} else {
var normal = Cartesian3.UNIT_X;
var position = ray.origin;
var direction = ray.direction;
distance = -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction);
}
handleZoom(controller, startPosition, movement, controller._zoomFactor, distance);
}
function updateCV(controller) {
var scene = controller._scene;
var camera = scene.camera;
if (!Matrix4.equals(Matrix4.IDENTITY, camera.transform)) {
reactToInput(controller, controller.enableRotate, controller.rotateEventTypes, rotate3D, controller.inertiaSpin, '_lastInertiaSpinMovement');
reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom3D, controller.inertiaZoom, '_lastInertiaZoomMovement');
} else {
var tweens = controller._tweens;
if (controller._aggregator.anyButtonDown) {
tweens.removeAll();
}
reactToInput(controller, controller.enableTilt, controller.tiltEventTypes, rotateCV, controller.inertiaSpin, '_lastInertiaTiltMovement');
reactToInput(controller, controller.enableTranslate, controller.translateEventTypes, translateCV, controller.inertiaTranslate, '_lastInertiaTranslateMovement');
reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoomCV, controller.inertiaZoom, '_lastInertiaZoomMovement');
reactToInput(controller, controller.enableLook, controller.lookEventTypes, look3D);
if (!controller._aggregator.anyButtonDown &&
(!defined(controller._lastInertiaZoomMovement) || !controller._lastInertiaZoomMovement.active) &&
(!defined(controller._lastInertiaTranslateMovement) || !controller._lastInertiaTranslateMovement.active) &&
!tweens.contains(controller._tween)) {
var tween = camera.createCorrectPositionTween(controller.bounceAnimationTime);
if (defined(tween)) {
controller._tween = tweens.add(tween);
}
}
tweens.update();
}
}
var scratchStrafeRay = new Ray();
var scratchStrafePlane = new Plane(Cartesian3.ZERO, 0.0);
var scratchStrafeIntersection = new Cartesian3();
var scratchStrafeDirection = new Cartesian3();
function strafe(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
var mouseStartPosition = pickGlobe(controller, movement.startPosition, scratchMousePos);
if (!defined(mouseStartPosition)) {
return;
}
var mousePosition = movement.endPosition;
var ray = camera.getPickRay(mousePosition, scratchStrafeRay);
var direction = Cartesian3.clone(camera.direction, scratchStrafeDirection);
if (scene.mode === SceneMode.COLUMBUS_VIEW) {
Cartesian3.fromElements(direction.z, direction.x, direction.y, direction);
}
var plane = Plane.fromPointNormal(mouseStartPosition, direction, scratchStrafePlane);
var intersection = IntersectionTests.rayPlane(ray, plane, scratchStrafeIntersection);
if (!defined(intersection)) {
return;
}
direction = Cartesian3.subtract(mouseStartPosition, intersection, direction);
if (scene.mode === SceneMode.COLUMBUS_VIEW) {
Cartesian3.fromElements(direction.y, direction.z, direction.x, direction);
}
Cartesian3.add(camera.position, direction, camera.position);
}
var spin3DPick = new Cartesian3();
var scratchCartographic = new Cartographic();
var scratchMousePos = new Cartesian3();
var scratchRadii = new Cartesian3();
var scratchEllipsoid = new Ellipsoid();
var scratchLookUp = new Cartesian3();
function spin3D(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
rotate3D(controller, startPosition, movement);
return;
}
var magnitude;
var radii;
var ellipsoid;
var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, scratchLookUp);
var height = controller._ellipsoid.cartesianToCartographic(camera.positionWC, scratchCartographic).height;
var globe = controller._globe;
var mousePos;
var tangentPick = false;
if (defined(globe) && height < controller._minimumPickingTerrainHeight) {
mousePos = pickGlobe(controller, movement.startPosition, scratchMousePos);
if (defined(mousePos)) {
var ray = camera.getPickRay(movement.startPosition, pickGlobeScratchRay);
var normal = controller._ellipsoid.geodeticSurfaceNormal(mousePos);
tangentPick = Math.abs(Cartesian3.dot(ray.direction, normal)) < 0.05;
if (tangentPick && !controller._looking) {
controller._rotating = false;
controller._strafing = true;
}
}
}
if (Cartesian2.equals(startPosition, controller._rotateMousePosition)) {
if (controller._looking) {
look3D(controller, startPosition, movement, up);
} else if (controller._rotating) {
rotate3D(controller, startPosition, movement);
} else if (controller._strafing) {
Cartesian3.clone(mousePos, controller._strafeStartPosition);
strafe(controller, startPosition, movement);
} else {
magnitude = Cartesian3.magnitude(controller._rotateStartPosition);
radii = scratchRadii;
radii.x = radii.y = radii.z = magnitude;
ellipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);
pan3D(controller, startPosition, movement, ellipsoid);
}
return;
} else {
controller._looking = false;
controller._rotating = false;
controller._strafing = false;
}
if (defined(globe) && height < controller._minimumPickingTerrainHeight) {
if (defined(mousePos)) {
if (Cartesian3.magnitude(camera.position) < Cartesian3.magnitude(mousePos)) {
Cartesian3.clone(mousePos, controller._strafeStartPosition);
controller._strafing = true;
strafe(controller, startPosition, movement);
} else {
magnitude = Cartesian3.magnitude(mousePos);
radii = scratchRadii;
radii.x = radii.y = radii.z = magnitude;
ellipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);
pan3D(controller, startPosition, movement, ellipsoid);
Cartesian3.clone(mousePos, controller._rotateStartPosition);
}
} else {
controller._looking = true;
look3D(controller, startPosition, movement, up);
}
} else if (defined(camera.pickEllipsoid(movement.startPosition, controller._ellipsoid, spin3DPick))) {
pan3D(controller, startPosition, movement, controller._ellipsoid);
Cartesian3.clone(spin3DPick, controller._rotateStartPosition);
} else if (height > controller._minimumTrackBallHeight) {
controller._rotating = true;
rotate3D(controller, startPosition, movement);
} else {
controller._looking = true;
look3D(controller, startPosition, movement, up);
}
Cartesian2.clone(startPosition, controller._rotateMousePosition);
}
function rotate3D(controller, startPosition, movement, constrainedAxis, rotateOnlyVertical, rotateOnlyHorizontal) {
rotateOnlyVertical = defaultValue(rotateOnlyVertical, false);
rotateOnlyHorizontal = defaultValue(rotateOnlyHorizontal, false);
var scene = controller._scene;
var camera = scene.camera;
var canvas = scene.canvas;
var oldAxis = camera.constrainedAxis;
if (defined(constrainedAxis)) {
camera.constrainedAxis = constrainedAxis;
}
var rho = Cartesian3.magnitude(camera.position);
var rotateRate = controller._rotateFactor * (rho - controller._rotateRateRangeAdjustment);
if (rotateRate > controller._maximumRotateRate) {
rotateRate = controller._maximumRotateRate;
}
if (rotateRate < controller._minimumRotateRate) {
rotateRate = controller._minimumRotateRate;
}
var phiWindowRatio = (movement.startPosition.x - movement.endPosition.x) / canvas.clientWidth;
var thetaWindowRatio = (movement.startPosition.y - movement.endPosition.y) / canvas.clientHeight;
phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio);
thetaWindowRatio = Math.min(thetaWindowRatio, controller.maximumMovementRatio);
var deltaPhi = rotateRate * phiWindowRatio * Math.PI * 2.0;
var deltaTheta = rotateRate * thetaWindowRatio * Math.PI;
if (!rotateOnlyVertical) {
camera.rotateRight(deltaPhi);
}
if (!rotateOnlyHorizontal) {
camera.rotateUp(deltaTheta);
}
camera.constrainedAxis = oldAxis;
}
var pan3DP0 = Cartesian4.clone(Cartesian4.UNIT_W);
var pan3DP1 = Cartesian4.clone(Cartesian4.UNIT_W);
var pan3DTemp0 = new Cartesian3();
var pan3DTemp1 = new Cartesian3();
var pan3DTemp2 = new Cartesian3();
var pan3DTemp3 = new Cartesian3();
var pan3DStartMousePosition = new Cartesian2();
var pan3DEndMousePosition = new Cartesian2();
function pan3D(controller, startPosition, movement, ellipsoid) {
var scene = controller._scene;
var camera = scene.camera;
var startMousePosition = Cartesian2.clone(movement.startPosition, pan3DStartMousePosition);
var endMousePosition = Cartesian2.clone(movement.endPosition, pan3DEndMousePosition);
var p0 = camera.pickEllipsoid(startMousePosition, ellipsoid, pan3DP0);
var p1 = camera.pickEllipsoid(endMousePosition, ellipsoid, pan3DP1);
if (!defined(p0) || !defined(p1)) {
controller._rotating = true;
rotate3D(controller, startPosition, movement);
return;
}
p0 = camera.worldToCameraCoordinates(p0, p0);
p1 = camera.worldToCameraCoordinates(p1, p1);
if (!defined(camera.constrainedAxis)) {
Cartesian3.normalize(p0, p0);
Cartesian3.normalize(p1, p1);
var dot = Cartesian3.dot(p0, p1);
var axis = Cartesian3.cross(p0, p1, pan3DTemp0);
if (dot < 1.0 && !Cartesian3.equalsEpsilon(axis, Cartesian3.ZERO, CesiumMath.EPSILON14)) { // dot is in [0, 1]
var angle = Math.acos(dot);
camera.rotate(axis, angle);
}
} else {
var basis0 = camera.constrainedAxis;
var basis1 = Cartesian3.mostOrthogonalAxis(basis0, pan3DTemp0);
Cartesian3.cross(basis1, basis0, basis1);
Cartesian3.normalize(basis1, basis1);
var basis2 = Cartesian3.cross(basis0, basis1, pan3DTemp1);
var startRho = Cartesian3.magnitude(p0);
var startDot = Cartesian3.dot(basis0, p0);
var startTheta = Math.acos(startDot / startRho);
var startRej = Cartesian3.multiplyByScalar(basis0, startDot, pan3DTemp2);
Cartesian3.subtract(p0, startRej, startRej);
Cartesian3.normalize(startRej, startRej);
var endRho = Cartesian3.magnitude(p1);
var endDot = Cartesian3.dot(basis0, p1);
var endTheta = Math.acos(endDot / endRho);
var endRej = Cartesian3.multiplyByScalar(basis0, endDot, pan3DTemp3);
Cartesian3.subtract(p1, endRej, endRej);
Cartesian3.normalize(endRej, endRej);
var startPhi = Math.acos(Cartesian3.dot(startRej, basis1));
if (Cartesian3.dot(startRej, basis2) < 0) {
startPhi = CesiumMath.TWO_PI - startPhi;
}
var endPhi = Math.acos(Cartesian3.dot(endRej, basis1));
if (Cartesian3.dot(endRej, basis2) < 0) {
endPhi = CesiumMath.TWO_PI - endPhi;
}
var deltaPhi = startPhi - endPhi;
var east;
if (Cartesian3.equalsEpsilon(basis0, camera.position, CesiumMath.EPSILON2)) {
east = camera.right;
} else {
east = Cartesian3.cross(basis0, camera.position, pan3DTemp0);
}
var planeNormal = Cartesian3.cross(basis0, east, pan3DTemp0);
var side0 = Cartesian3.dot(planeNormal, Cartesian3.subtract(p0, basis0, pan3DTemp1));
var side1 = Cartesian3.dot(planeNormal, Cartesian3.subtract(p1, basis0, pan3DTemp1));
var deltaTheta;
if (side0 > 0 && side1 > 0) {
deltaTheta = endTheta - startTheta;
} else if (side0 > 0 && side1 <= 0) {
if (Cartesian3.dot(camera.position, basis0) > 0) {
deltaTheta = -startTheta - endTheta;
} else {
deltaTheta = startTheta + endTheta;
}
} else {
deltaTheta = startTheta - endTheta;
}
camera.rotateRight(deltaPhi);
camera.rotateUp(deltaTheta);
}
}
var zoom3DUnitPosition = new Cartesian3();
var zoom3DCartographic = new Cartographic();
function zoom3D(controller, startPosition, movement) {
if (defined(movement.distance)) {
movement = movement.distance;
}
var ellipsoid = controller._ellipsoid;
var scene = controller._scene;
var camera = scene.camera;
var canvas = scene.canvas;
var windowPosition = zoomCVWindowPos;
windowPosition.x = canvas.clientWidth / 2;
windowPosition.y = canvas.clientHeight / 2;
var ray = camera.getPickRay(windowPosition, zoomCVWindowRay);
var intersection;
var height = ellipsoid.cartesianToCartographic(camera.position, zoom3DCartographic).height;
if (height < controller._minimumPickingTerrainHeight) {
intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);
}
var distance;
if (defined(intersection)) {
distance = Cartesian3.distance(ray.origin, intersection);
} else {
distance = height;
}
var unitPosition = Cartesian3.normalize(camera.position, zoom3DUnitPosition);
handleZoom(controller, startPosition, movement, controller._zoomFactor, distance, Cartesian3.dot(unitPosition, camera.direction));
}
var tilt3DWindowPos = new Cartesian2();
var tilt3DRay = new Ray();
var tilt3DCenter = new Cartesian3();
var tilt3DVerticalCenter = new Cartesian3();
var tilt3DTransform = new Matrix4();
var tilt3DVerticalTransform = new Matrix4();
var tilt3DCartesian3 = new Cartesian3();
var tilt3DOldTransform = new Matrix4();
var tilt3DQuaternion = new Quaternion();
var tilt3DMatrix = new Matrix3();
var tilt3DCart = new Cartographic();
var tilt3DLookUp = new Cartesian3();
function tilt3D(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
return;
}
if (defined(movement.angleAndHeight)) {
movement = movement.angleAndHeight;
}
if (!Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
controller._tiltOnEllipsoid = false;
controller._looking = false;
}
if (controller._looking) {
var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, tilt3DLookUp);
look3D(controller, startPosition, movement, up);
return;
}
var ellipsoid = controller._ellipsoid;
var cartographic = ellipsoid.cartesianToCartographic(camera.position, tilt3DCart);
if (controller._tiltOnEllipsoid || cartographic.height > controller._minimumCollisionTerrainHeight) {
controller._tiltOnEllipsoid = true;
tilt3DOnEllipsoid(controller, startPosition, movement);
} else {
tilt3DOnTerrain(controller, startPosition, movement);
}
}
var tilt3DOnEllipsoidCartographic = new Cartographic();
function tilt3DOnEllipsoid(controller, startPosition, movement) {
var ellipsoid = controller._ellipsoid;
var scene = controller._scene;
var camera = scene.camera;
var minHeight = controller.minimumZoomDistance * 0.25;
var height = ellipsoid.cartesianToCartographic(camera.positionWC, tilt3DOnEllipsoidCartographic).height;
if (height - minHeight - 1.0 < CesiumMath.EPSILON3 &&
movement.endPosition.y - movement.startPosition.y < 0) {
return;
}
var canvas = scene.canvas;
var windowPosition = tilt3DWindowPos;
windowPosition.x = canvas.clientWidth / 2;
windowPosition.y = canvas.clientHeight / 2;
var ray = camera.getPickRay(windowPosition, tilt3DRay);
var center;
var intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);
if (defined(intersection)) {
center = Ray.getPoint(ray, intersection.start, tilt3DCenter);
} else if (height > controller._minimumTrackBallHeight) {
var grazingAltitudeLocation = IntersectionTests.grazingAltitudeLocation(ray, ellipsoid);
if (!defined(grazingAltitudeLocation)) {
return;
}
var grazingAltitudeCart = ellipsoid.cartesianToCartographic(grazingAltitudeLocation, tilt3DCart);
grazingAltitudeCart.height = 0.0;
center = ellipsoid.cartographicToCartesian(grazingAltitudeCart, tilt3DCenter);
} else {
controller._looking = true;
var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, tilt3DLookUp);
look3D(controller, startPosition, movement, up);
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
return;
}
var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, tilt3DTransform);
var oldGlobe = controller._globe;
var oldEllipsoid = controller._ellipsoid;
controller._globe = undefined;
controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
controller._rotateFactor = 1.0;
controller._rotateRateRangeAdjustment = 1.0;
var oldTransform = Matrix4.clone(camera.transform, tilt3DOldTransform);
camera._setTransform(transform);
rotate3D(controller, startPosition, movement, Cartesian3.UNIT_Z);
camera._setTransform(oldTransform);
controller._globe = oldGlobe;
controller._ellipsoid = oldEllipsoid;
var radius = oldEllipsoid.maximumRadius;
controller._rotateFactor = 1.0 / radius;
controller._rotateRateRangeAdjustment = radius;
}
function tilt3DOnTerrain(controller, startPosition, movement) {
var ellipsoid = controller._ellipsoid;
var scene = controller._scene;
var camera = scene.camera;
var center;
var ray;
var intersection;
if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
center = Cartesian3.clone(controller._tiltCenter, tilt3DCenter);
} else {
center = pickGlobe(controller, startPosition, tilt3DCenter);
if (!defined(center)) {
ray = camera.getPickRay(startPosition, tilt3DRay);
intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);
if (!defined(intersection)) {
var cartographic = ellipsoid.cartesianToCartographic(camera.position, tilt3DCart);
if (cartographic.height <= controller._minimumTrackBallHeight) {
controller._looking = true;
var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, tilt3DLookUp);
look3D(controller, startPosition, movement, up);
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
}
return;
}
center = Ray.getPoint(ray, intersection.start, tilt3DCenter);
}
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
Cartesian3.clone(center, controller._tiltCenter);
}
var canvas = scene.canvas;
var windowPosition = tilt3DWindowPos;
windowPosition.x = canvas.clientWidth / 2;
windowPosition.y = controller._tiltCenterMousePosition.y;
ray = camera.getPickRay(windowPosition, tilt3DRay);
var mag = Cartesian3.magnitude(center);
var radii = Cartesian3.fromElements(mag, mag, mag, scratchRadii);
var newEllipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);
intersection = IntersectionTests.rayEllipsoid(ray, newEllipsoid);
if (!defined(intersection)) {
return;
}
var t = Cartesian3.magnitude(ray.origin) > mag ? intersection.start : intersection.stop;
var verticalCenter = Ray.getPoint(ray, t, tilt3DVerticalCenter);
var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, tilt3DTransform);
var verticalTransform = Transforms.eastNorthUpToFixedFrame(verticalCenter, newEllipsoid, tilt3DVerticalTransform);
var oldGlobe = controller._globe;
var oldEllipsoid = controller._ellipsoid;
controller._globe = undefined;
controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
controller._rotateFactor = 1.0;
controller._rotateRateRangeAdjustment = 1.0;
var constrainedAxis = Cartesian3.UNIT_Z;
var oldTransform = Matrix4.clone(camera.transform, tilt3DOldTransform);
camera._setTransform(transform);
var tangent = Cartesian3.cross(verticalCenter, camera.positionWC, tilt3DCartesian3);
var dot = Cartesian3.dot(camera.rightWC, tangent);
rotate3D(controller, startPosition, movement, constrainedAxis, false, true);
camera._setTransform(verticalTransform);
if (dot < 0.0) {
if (movement.startPosition.y > movement.endPosition.y) {
constrainedAxis = undefined;
}
var oldConstrainedAxis = camera.constrainedAxis;
camera.constrainedAxis = undefined;
rotate3D(controller, startPosition, movement, constrainedAxis, true, false);
camera.constrainedAxis = oldConstrainedAxis;
} else {
rotate3D(controller, startPosition, movement, constrainedAxis, true, false);
}
if (defined(camera.constrainedAxis)) {
var right = Cartesian3.cross(camera.direction, camera.constrainedAxis, tilt3DCartesian3);
if (!Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
if (Cartesian3.dot(right, camera.right) < 0.0) {
Cartesian3.negate(right, right);
}
Cartesian3.cross(right, camera.direction, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.normalize(camera.up, camera.up);
Cartesian3.normalize(camera.right, camera.right);
}
}
camera._setTransform(oldTransform);
controller._globe = oldGlobe;
controller._ellipsoid = oldEllipsoid;
var radius = oldEllipsoid.maximumRadius;
controller._rotateFactor = 1.0 / radius;
controller._rotateRateRangeAdjustment = radius;
var originalPosition = Cartesian3.clone(camera.positionWC, tilt3DCartesian3);
camera._adjustHeightForTerrain();
if (!Cartesian3.equals(camera.positionWC, originalPosition)) {
camera._setTransform(verticalTransform);
camera.worldToCameraCoordinatesPoint(originalPosition, originalPosition);
var magSqrd = Cartesian3.magnitudeSquared(originalPosition);
if (Cartesian3.magnitudeSquared(camera.position) > magSqrd) {
Cartesian3.normalize(camera.position, camera.position);
Cartesian3.multiplyByScalar(camera.position, Math.sqrt(magSqrd), camera.position);
}
var angle = Cartesian3.angleBetween(originalPosition, camera.position);
var axis = Cartesian3.cross(originalPosition, camera.position, originalPosition);
Cartesian3.normalize(axis, axis);
var quaternion = Quaternion.fromAxisAngle(axis, angle, tilt3DQuaternion);
var rotation = Matrix3.fromQuaternion(quaternion, tilt3DMatrix);
Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
Matrix3.multiplyByVector(rotation, camera.up, camera.up);
Cartesian3.cross(camera.direction, camera.up, camera.right);
Cartesian3.cross(camera.right, camera.direction, camera.up);
camera._setTransform(oldTransform);
}
}
var look3DStartPos = new Cartesian2();
var look3DEndPos = new Cartesian2();
var look3DStartRay = new Ray();
var look3DEndRay = new Ray();
var look3DNegativeRot = new Cartesian3();
var look3DTan = new Cartesian3();
function look3D(controller, startPosition, movement, rotationAxis) {
var scene = controller._scene;
var camera = scene.camera;
var startPos = look3DStartPos;
startPos.x = movement.startPosition.x;
startPos.y = 0.0;
var endPos = look3DEndPos;
endPos.x = movement.endPosition.x;
endPos.y = 0.0;
var start = camera.getPickRay(startPos, look3DStartRay).direction;
var end = camera.getPickRay(endPos, look3DEndRay).direction;
var angle = 0.0;
var dot = Cartesian3.dot(start, end);
if (dot < 1.0) { // dot is in [0, 1]
angle = Math.acos(dot);
}
angle = (movement.startPosition.x > movement.endPosition.x) ? -angle : angle;
var horizontalRotationAxis = controller._horizontalRotationAxis;
if (defined(rotationAxis)) {
camera.look(rotationAxis, -angle);
} else if (defined(horizontalRotationAxis)) {
camera.look(horizontalRotationAxis, -angle);
} else {
camera.lookLeft(angle);
}
startPos.x = 0.0;
startPos.y = movement.startPosition.y;
endPos.x = 0.0;
endPos.y = movement.endPosition.y;
start = camera.getPickRay(startPos, look3DStartRay).direction;
end = camera.getPickRay(endPos, look3DEndRay).direction;
angle = 0.0;
dot = Cartesian3.dot(start, end);
if (dot < 1.0) { // dot is in [0, 1]
angle = Math.acos(dot);
}
angle = (movement.startPosition.y > movement.endPosition.y) ? -angle : angle;
rotationAxis = defaultValue(rotationAxis, horizontalRotationAxis);
if (defined(rotationAxis)) {
var direction = camera.direction;
var negativeRotationAxis = Cartesian3.negate(rotationAxis, look3DNegativeRot);
var northParallel = Cartesian3.equalsEpsilon(direction, rotationAxis, CesiumMath.EPSILON2);
var southParallel = Cartesian3.equalsEpsilon(direction, negativeRotationAxis, CesiumMath.EPSILON2);
if ((!northParallel && !southParallel)) {
dot = Cartesian3.dot(direction, rotationAxis);
var angleToAxis = CesiumMath.acosClamped(dot);
if (angle > 0 && angle > angleToAxis) {
angle = angleToAxis - CesiumMath.EPSILON4;
}
dot = Cartesian3.dot(direction, negativeRotationAxis);
angleToAxis = CesiumMath.acosClamped(dot);
if (angle < 0 && -angle > angleToAxis) {
angle = -angleToAxis + CesiumMath.EPSILON4;
}
var tangent = Cartesian3.cross(rotationAxis, direction, look3DTan);
camera.look(tangent, angle);
} else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {
camera.look(camera.right, -angle);
}
} else {
camera.lookUp(angle);
}
}
function update3D(controller) {
reactToInput(controller, controller.enableRotate, controller.rotateEventTypes, spin3D, controller.inertiaSpin, '_lastInertiaSpinMovement');
reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom3D, controller.inertiaZoom, '_lastInertiaZoomMovement');
reactToInput(controller, controller.enableTilt, controller.tiltEventTypes, tilt3D, controller.inertiaSpin, '_lastInertiaTiltMovement');
reactToInput(controller, controller.enableLook, controller.lookEventTypes, look3D);
}
/**
* @private
*/
ScreenSpaceCameraController.prototype.update = function() {
if (!Matrix4.equals(this._scene.camera.transform, Matrix4.IDENTITY)) {
this._globe = undefined;
this._ellipsoid = Ellipsoid.UNIT_SPHERE;
} else {
this._globe = this._scene.globe;
this._ellipsoid = defined(this._globe) ? this._globe.ellipsoid : this._scene.mapProjection.ellipsoid;
}
this._minimumCollisionTerrainHeight = this.minimumCollisionTerrainHeight * this._scene.terrainExaggeration;
this._minimumPickingTerrainHeight = this.minimumPickingTerrainHeight * this._scene.terrainExaggeration;
this._minimumTrackBallHeight = this.minimumTrackBallHeight * this._scene.terrainExaggeration;
var radius = this._ellipsoid.maximumRadius;
this._rotateFactor = 1.0 / radius;
this._rotateRateRangeAdjustment = radius;
var scene = this._scene;
var mode = scene.mode;
if (mode === SceneMode.SCENE2D) {
update2D(this);
} else if (mode === SceneMode.COLUMBUS_VIEW) {
this._horizontalRotationAxis = Cartesian3.UNIT_Z;
updateCV(this);
} else if (mode === SceneMode.SCENE3D) {
this._horizontalRotationAxis = undefined;
update3D(this);
}
this._aggregator.reset();
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see ScreenSpaceCameraController#destroy
*/
ScreenSpaceCameraController.prototype.isDestroyed = function() {
return false;
};
/**
* Removes mouse listeners held by this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* controller = controller && controller.destroy();
*
* @see ScreenSpaceCameraController#isDestroyed
*/
ScreenSpaceCameraController.prototype.destroy = function() {
this._tweens.removeAll();
this._aggregator = this._aggregator && this._aggregator.destroy();
return destroyObject(this);
};
return ScreenSpaceCameraController;
});
/*global define*/
define('Scene/ShadowMapShader',[
'../Core/defined',
'../Renderer/ShaderSource'
], function(
defined,
ShaderSource) {
'use strict';
/**
* @private
*/
function ShadowMapShader() {
}
ShadowMapShader.createShadowCastVertexShader = function(vs, isPointLight, isTerrain) {
var defines = vs.defines.slice(0);
var sources = vs.sources.slice(0);
if (isTerrain) {
defines.push('GENERATE_POSITION');
}
var positionVaryingName = ShaderSource.findPositionVarying(vs);
var hasPositionVarying = defined(positionVaryingName);
if (isPointLight && !hasPositionVarying) {
var length = sources.length;
for (var j = 0; j < length; ++j) {
sources[j] = ShaderSource.replaceMain(sources[j], 'czm_shadow_cast_main');
}
var shadowVS =
'varying vec3 v_positionEC; \n' +
'void main() \n' +
'{ \n' +
' czm_shadow_cast_main(); \n' +
' v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n' +
'}';
sources.push(shadowVS);
}
return new ShaderSource({
defines : defines,
sources : sources
});
};
ShadowMapShader.createShadowCastFragmentShader = function(fs, isPointLight, usesDepthTexture, opaque) {
var defines = fs.defines.slice(0);
var sources = fs.sources.slice(0);
var positionVaryingName = ShaderSource.findPositionVarying(fs);
var hasPositionVarying = defined(positionVaryingName);
if (!hasPositionVarying) {
positionVaryingName = 'v_positionEC';
}
var length = sources.length;
for (var i = 0; i < length; ++i) {
sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_cast_main');
}
var fsSource = '';
if (isPointLight) {
if (!hasPositionVarying) {
fsSource += 'varying vec3 v_positionEC; \n';
}
fsSource += 'uniform vec4 shadowMap_lightPositionEC; \n';
}
if (opaque) {
fsSource +=
'void main() \n' +
'{ \n';
} else {
fsSource +=
'void main() \n' +
'{ \n' +
' czm_shadow_cast_main(); \n' +
' if (gl_FragColor.a == 0.0) \n' +
' { \n' +
' discard; \n' +
' } \n';
}
if (isPointLight) {
fsSource +=
'float distance = length(' + positionVaryingName + '); \n' +
'distance /= shadowMap_lightPositionEC.w; // radius \n' +
'gl_FragColor = czm_packDepth(distance); \n';
} else if (usesDepthTexture) {
fsSource += 'gl_FragColor = vec4(1.0); \n';
} else {
fsSource += 'gl_FragColor = czm_packDepth(gl_FragCoord.z); \n';
}
fsSource += '} \n';
sources.push(fsSource);
return new ShaderSource({
defines : defines,
sources : sources
});
};
ShadowMapShader.createShadowReceiveVertexShader = function(vs, isTerrain, hasTerrainNormal) {
var defines = vs.defines.slice(0);
var sources = vs.sources.slice(0);
if (isTerrain) {
if (hasTerrainNormal) {
defines.push('GENERATE_POSITION_AND_NORMAL');
} else {
defines.push('GENERATE_POSITION');
}
}
return new ShaderSource({
defines : defines,
sources : sources
});
};
ShadowMapShader.createShadowReceiveFragmentShader = function(fs, shadowMap, castShadows, isTerrain, hasTerrainNormal) {
var normalVaryingName = ShaderSource.findNormalVarying(fs);
var hasNormalVarying = (!isTerrain && defined(normalVaryingName)) || (isTerrain && hasTerrainNormal);
var positionVaryingName = ShaderSource.findPositionVarying(fs);
var hasPositionVarying = defined(positionVaryingName);
var usesDepthTexture = shadowMap._usesDepthTexture;
var polygonOffsetSupported = shadowMap._polygonOffsetSupported;
var isPointLight = shadowMap._isPointLight;
var isSpotLight = shadowMap._isSpotLight;
var hasCascades = shadowMap._numberOfCascades > 1;
var debugCascadeColors = shadowMap.debugCascadeColors;
var softShadows = shadowMap.softShadows;
var bias = isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);
var defines = fs.defines.slice(0);
var sources = fs.sources.slice(0);
var length = sources.length;
for (var i = 0; i < length; ++i) {
sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_receive_main');
}
if (isPointLight) {
defines.push('USE_CUBE_MAP_SHADOW');
} else if (usesDepthTexture) {
defines.push('USE_SHADOW_DEPTH_TEXTURE');
}
if (softShadows && !isPointLight) {
defines.push('USE_SOFT_SHADOWS');
}
// Enable day-night shading so that the globe is dark when the light is below the horizon
if (hasCascades && castShadows && isTerrain) {
if (hasNormalVarying) {
defines.push('ENABLE_VERTEX_LIGHTING');
} else {
defines.push('ENABLE_DAYNIGHT_SHADING');
}
}
if (castShadows && bias.normalShading && hasNormalVarying) {
defines.push('USE_NORMAL_SHADING');
if (bias.normalShadingSmooth > 0.0) {
defines.push('USE_NORMAL_SHADING_SMOOTH');
}
}
var fsSource = '';
if (isPointLight) {
fsSource += 'uniform samplerCube shadowMap_textureCube; \n';
} else {
fsSource += 'uniform sampler2D shadowMap_texture; \n';
}
fsSource +=
'uniform mat4 shadowMap_matrix; \n' +
'uniform vec3 shadowMap_lightDirectionEC; \n' +
'uniform vec4 shadowMap_lightPositionEC; \n' +
'uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness; \n' +
'uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth; \n' +
'vec4 getPositionEC() \n' +
'{ \n' +
(hasPositionVarying ?
' return vec4(' + positionVaryingName + ', 1.0); \n' :
' return czm_windowToEyeCoordinates(gl_FragCoord); \n') +
'} \n' +
'vec3 getNormalEC() \n' +
'{ \n' +
(hasNormalVarying ?
' return normalize(' + normalVaryingName + '); \n' :
' return vec3(1.0); \n') +
'} \n' +
// Offset the shadow position in the direction of the normal for perpendicular and back faces
'void applyNormalOffset(inout vec4 positionEC, vec3 normalEC, float nDotL) \n' +
'{ \n' +
(bias.normalOffset && hasNormalVarying ?
' float normalOffset = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.x; \n' +
' float normalOffsetScale = 1.0 - nDotL; \n' +
' vec3 offset = normalOffset * normalOffsetScale * normalEC; \n' +
' positionEC.xyz += offset; \n' : '') +
'} \n';
fsSource +=
'void main() \n' +
'{ \n' +
' czm_shadow_receive_main(); \n' +
' vec4 positionEC = getPositionEC(); \n' +
' vec3 normalEC = getNormalEC(); \n' +
' float depth = -positionEC.z; \n';
fsSource +=
' czm_shadowParameters shadowParameters; \n' +
' shadowParameters.texelStepSize = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy; \n' +
' shadowParameters.depthBias = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z; \n' +
' shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w; \n' +
' shadowParameters.darkness = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w; \n';
if (isTerrain) {
// Scale depth bias based on view distance to reduce z-fighting in distant terrain
fsSource += ' shadowParameters.depthBias *= max(depth * 0.01, 1.0); \n';
} else if (!polygonOffsetSupported) {
// If polygon offset isn't supported push the depth back based on view, however this
// causes light leaking at further away views
fsSource += ' shadowParameters.depthBias *= mix(1.0, 100.0, depth * 0.0015); \n';
}
if (isPointLight) {
fsSource +=
' vec3 directionEC = positionEC.xyz - shadowMap_lightPositionEC.xyz; \n' +
' float distance = length(directionEC); \n' +
' directionEC = normalize(directionEC); \n' +
' float radius = shadowMap_lightPositionEC.w; \n' +
' // Stop early if the fragment is beyond the point light radius \n' +
' if (distance > radius) \n' +
' { \n' +
' return; \n' +
' } \n' +
' vec3 directionWC = czm_inverseViewRotation * directionEC; \n' +
' shadowParameters.depth = distance / radius; \n' +
' shadowParameters.nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n' +
' shadowParameters.texCoords = directionWC; \n' +
' float visibility = czm_shadowVisibility(shadowMap_textureCube, shadowParameters); \n';
} else if (isSpotLight) {
fsSource +=
' vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz); \n' +
' float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n' +
' applyNormalOffset(positionEC, normalEC, nDotL); \n' +
' vec4 shadowPosition = shadowMap_matrix * positionEC; \n' +
' // Spot light uses a perspective projection, so perform the perspective divide \n' +
' shadowPosition /= shadowPosition.w; \n' +
' // Stop early if the fragment is not in the shadow bounds \n' +
' if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n' +
' { \n' +
' return; \n' +
' } \n' +
' shadowParameters.texCoords = shadowPosition.xy; \n' +
' shadowParameters.depth = shadowPosition.z; \n' +
' shadowParameters.nDotL = nDotL; \n' +
' float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n';
} else if (hasCascades) {
fsSource +=
' float maxDepth = shadowMap_cascadeSplits[1].w; \n' +
' // Stop early if the eye depth exceeds the last cascade \n' +
' if (depth > maxDepth) \n' +
' { \n' +
' return; \n' +
' } \n' +
' // Get the cascade based on the eye-space depth \n' +
' vec4 weights = czm_cascadeWeights(depth); \n' +
' // Apply normal offset \n' +
' float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n' +
' applyNormalOffset(positionEC, normalEC, nDotL); \n' +
' // Transform position into the cascade \n' +
' vec4 shadowPosition = czm_cascadeMatrix(weights) * positionEC; \n' +
' // Get visibility \n' +
' shadowParameters.texCoords = shadowPosition.xy; \n' +
' shadowParameters.depth = shadowPosition.z; \n' +
' shadowParameters.nDotL = nDotL; \n' +
' float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n' +
' // Fade out shadows that are far away \n' +
' float shadowMapMaximumDistance = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.z; \n' +
' float fade = max((depth - shadowMapMaximumDistance * 0.8) / (shadowMapMaximumDistance * 0.2), 0.0); \n' +
' visibility = mix(visibility, 1.0, fade); \n' +
(debugCascadeColors ?
' // Draw cascade colors for debugging \n' +
' gl_FragColor *= czm_cascadeColor(weights); \n' : '');
} else {
fsSource +=
' float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n' +
' applyNormalOffset(positionEC, normalEC, nDotL); \n' +
' vec4 shadowPosition = shadowMap_matrix * positionEC; \n' +
' // Stop early if the fragment is not in the shadow bounds \n' +
' if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n' +
' { \n' +
' return; \n' +
' } \n' +
' shadowParameters.texCoords = shadowPosition.xy; \n' +
' shadowParameters.depth = shadowPosition.z; \n' +
' shadowParameters.nDotL = nDotL; \n' +
' float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n';
}
fsSource +=
' gl_FragColor.rgb *= visibility; \n' +
'} \n';
sources.push(fsSource);
return new ShaderSource({
defines : defines,
sources : sources
});
};
return ShadowMapShader;
});
/*global define*/
define('Scene/ShadowMap',[
'../Core/BoundingRectangle',
'../Core/BoundingSphere',
'../Core/BoxOutlineGeometry',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/clone',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/FeatureDetection',
'../Core/GeometryInstance',
'../Core/Intersect',
'../Core/Math',
'../Core/Matrix4',
'../Core/PixelFormat',
'../Core/Quaternion',
'../Core/SphereOutlineGeometry',
'../Core/WebGLConstants',
'../Renderer/ClearCommand',
'../Renderer/ContextLimits',
'../Renderer/CubeMap',
'../Renderer/DrawCommand',
'../Renderer/Framebuffer',
'../Renderer/Pass',
'../Renderer/PassState',
'../Renderer/PixelDatatype',
'../Renderer/Renderbuffer',
'../Renderer/RenderbufferFormat',
'../Renderer/RenderState',
'../Renderer/Sampler',
'../Renderer/ShaderProgram',
'../Renderer/Texture',
'../Renderer/TextureMagnificationFilter',
'../Renderer/TextureMinificationFilter',
'../Renderer/TextureWrap',
'./Camera',
'./CullFace',
'./CullingVolume',
'./DebugCameraPrimitive',
'./OrthographicFrustum',
'./PerInstanceColorAppearance',
'./PerspectiveFrustum',
'./Primitive',
'./ShadowMapShader'
], function(
BoundingRectangle,
BoundingSphere,
BoxOutlineGeometry,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
clone,
Color,
ColorGeometryInstanceAttribute,
combine,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
FeatureDetection,
GeometryInstance,
Intersect,
CesiumMath,
Matrix4,
PixelFormat,
Quaternion,
SphereOutlineGeometry,
WebGLConstants,
ClearCommand,
ContextLimits,
CubeMap,
DrawCommand,
Framebuffer,
Pass,
PassState,
PixelDatatype,
Renderbuffer,
RenderbufferFormat,
RenderState,
Sampler,
ShaderProgram,
Texture,
TextureMagnificationFilter,
TextureMinificationFilter,
TextureWrap,
Camera,
CullFace,
CullingVolume,
DebugCameraPrimitive,
OrthographicFrustum,
PerInstanceColorAppearance,
PerspectiveFrustum,
Primitive,
ShadowMapShader) {
'use strict';
/**
* Creates a shadow map from the provided light camera.
*
* The normalOffset bias pushes the shadows forward slightly, and may be disabled
* for applications that require ultra precise shadows.
*
* @alias ShadowMap
* @constructor
*
* @param {Object} options An object containing the following properties:
* @param {Context} options.context The context in which to create the shadow map.
* @param {Camera} options.lightCamera A camera representing the light source.
* @param {Boolean} [options.enabled=true] Whether the shadow map is enabled.
* @param {Boolean} [options.isPointLight=false] Whether the light source is a point light. Point light shadows do not use cascades.
* @param {Boolean} [options.pointLightRadius=100.0] Radius of the point light.
* @param {Boolean} [options.cascadesEnabled=true] Use multiple shadow maps to cover different partitions of the view frustum.
* @param {Number} [options.numberOfCascades=4] The number of cascades to use for the shadow map. Supported values are one and four.
* @param {Number} [options.maximumDistance=5000.0] The maximum distance used for generating cascaded shadows. Lower values improve shadow quality.
* @param {Number} [options.size=2048] The width and height, in pixels, of each shadow map.
* @param {Boolean} [options.softShadows=false] Whether percentage-closer-filtering is enabled for producing softer shadows.
* @param {Number} [options.darkness=0.3] The shadow darkness.
*
* @exception {DeveloperError} Only one or four cascades are supported.
*
* @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Shadows.html|Cesium Sandcastle Shadows Demo}
*/
function ShadowMap(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var context = options.context;
if (!defined(context)) {
throw new DeveloperError('context is required.');
}
if (!defined(options.lightCamera)) {
throw new DeveloperError('lightCamera is required.');
}
if (defined(options.numberOfCascades) && ((options.numberOfCascades !== 1) && (options.numberOfCascades !== 4))) {
throw new DeveloperError('Only one or four cascades are supported.');
}
this._enabled = defaultValue(options.enabled, true);
this._softShadows = defaultValue(options.softShadows, false);
this.dirty = true;
/**
* Specifies whether the shadow map originates from a light source. Shadow maps that are used for analytical
* purposes should set this to false so as not to affect scene rendering.
*
* @private
*/
this.fromLightSource = defaultValue(options.fromLightSource, true);
/**
* Determines the darkness of the shadows.
*
* @type {Number}
* @default 0.3
*/
this.darkness = defaultValue(options.darkness, 0.3);
this._darkness = this.darkness;
/**
* Determines the maximum distance of the shadow map. Only applicable for cascaded shadows. Larger distances may result in lower quality shadows.
*
* @type {Number}
* @default 5000.0
*/
this.maximumDistance = defaultValue(options.maximumDistance, 5000.0);
this._outOfView = false;
this._outOfViewPrevious = false;
this._needsUpdate = true;
// In IE11 and Edge polygon offset is not functional.
// TODO : Also disabled for instances of Firefox and Chrome running ANGLE that do not support depth textures.
// Re-enable once https://github.com/AnalyticalGraphicsInc/cesium/issues/4560 is resolved.
var polygonOffsetSupported = true;
if (FeatureDetection.isInternetExplorer() || FeatureDetection.isEdge() || ((FeatureDetection.isChrome() || FeatureDetection.isFirefox()) && FeatureDetection.isWindows() && !context.depthTexture)) {
polygonOffsetSupported = false;
}
this._polygonOffsetSupported = polygonOffsetSupported;
this._terrainBias = {
polygonOffset : polygonOffsetSupported,
polygonOffsetFactor : 1.1,
polygonOffsetUnits : 4.0,
normalOffset : true,
normalOffsetScale : 0.5,
normalShading : true,
normalShadingSmooth : 0.3,
depthBias : 0.0001
};
this._primitiveBias = {
polygonOffset : polygonOffsetSupported,
polygonOffsetFactor : 1.1,
polygonOffsetUnits : 4.0,
normalOffset : true,
normalOffsetScale : 0.1,
normalShading : true,
normalShadingSmooth : 0.05,
depthBias : 0.00002
};
this._pointBias = {
polygonOffset : false,
polygonOffsetFactor : 1.1,
polygonOffsetUnits : 4.0,
normalOffset : false,
normalOffsetScale : 0.0,
normalShading : true,
normalShadingSmooth : 0.1,
depthBias : 0.0005
};
// Framebuffer resources
this._depthAttachment = undefined;
this._colorAttachment = undefined;
// Uniforms
this._shadowMapMatrix = new Matrix4();
this._shadowMapTexture = undefined;
this._lightDirectionEC = new Cartesian3();
this._lightPositionEC = new Cartesian4();
this._distance = 0.0;
this._lightCamera = options.lightCamera;
this._shadowMapCamera = new ShadowMapCamera();
this._shadowMapCullingVolume = undefined;
this._sceneCamera = undefined;
this._boundingSphere = new BoundingSphere();
this._isPointLight = defaultValue(options.isPointLight, false);
this._pointLightRadius = defaultValue(options.pointLightRadius, 100.0);
this._cascadesEnabled = this._isPointLight ? false : defaultValue(options.cascadesEnabled, true);
this._numberOfCascades = !this._cascadesEnabled ? 0 : defaultValue(options.numberOfCascades, 4);
this._fitNearFar = true;
this._maximumCascadeDistances = [25.0, 150.0, 700.0, Number.MAX_VALUE];
this._textureSize = new Cartesian2();
this._isSpotLight = false;
if (this._cascadesEnabled) {
// Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
this._shadowMapCamera.frustum = new OrthographicFrustum();
} else if (defined(this._lightCamera.frustum.fov)) {
// If the light camera uses a perspective frustum, then the light source is a spot light
this._isSpotLight = true;
}
// Uniforms
this._cascadeSplits = [new Cartesian4(), new Cartesian4()];
this._cascadeMatrices = [new Matrix4(), new Matrix4(), new Matrix4(), new Matrix4()];
this._cascadeDistances = new Cartesian4();
var numberOfPasses;
if (this._isPointLight) {
numberOfPasses = 6; // One shadow map for each direction
} else if (!this._cascadesEnabled) {
numberOfPasses = 1;
} else {
numberOfPasses = this._numberOfCascades;
}
this._passes = new Array(numberOfPasses);
for (var i = 0; i < numberOfPasses; ++i) {
this._passes[i] = new ShadowPass(context);
}
this.debugShow = false;
this.debugFreezeFrame = false;
this._debugFreezeFrame = false;
this._debugCascadeColors = false;
this._debugLightFrustum = undefined;
this._debugCameraFrustum = undefined;
this._debugCascadeFrustums = new Array(this._numberOfCascades);
this._debugShadowViewCommand = undefined;
this._usesDepthTexture = context.depthTexture;
if (this._isPointLight) {
this._usesDepthTexture = false;
}
// Create render states for shadow casters
this._primitiveRenderState = undefined;
this._terrainRenderState = undefined;
this._pointRenderState = undefined;
createRenderStates(this);
// For clearing the shadow map texture every frame
this._clearCommand = new ClearCommand({
depth : 1.0,
color : new Color()
});
this._clearPassState = new PassState(context);
this._size = defaultValue(options.size, 2048);
this.size = this._size;
}
/**
* Global maximum shadow distance used to prevent far off receivers from extending
* the shadow far plane. This helps set a tighter near/far when viewing objects from space.
*
* @private
*/
ShadowMap.MAXIMUM_DISTANCE = 20000.0;
function ShadowPass(context) {
this.camera = new ShadowMapCamera();
this.passState = new PassState(context);
this.framebuffer = undefined;
this.textureOffsets = undefined;
this.commandList = [];
this.cullingVolume = undefined;
}
function createRenderState(colorMask, bias) {
return RenderState.fromCache({
cull : {
enabled : true,
face : CullFace.BACK
},
depthTest : {
enabled : true
},
colorMask : {
red : colorMask,
green : colorMask,
blue : colorMask,
alpha : colorMask
},
depthMask : true,
polygonOffset : {
enabled : bias.polygonOffset,
factor : bias.polygonOffsetFactor,
units : bias.polygonOffsetUnits
}
});
}
function createRenderStates(shadowMap) {
// Enable the color mask if the shadow map is backed by a color texture, e.g. when depth textures aren't supported
var colorMask = !shadowMap._usesDepthTexture;
shadowMap._primitiveRenderState = createRenderState(colorMask, shadowMap._primitiveBias);
shadowMap._terrainRenderState = createRenderState(colorMask, shadowMap._terrainBias);
shadowMap._pointRenderState = createRenderState(colorMask, shadowMap._pointBias);
}
/**
* @private
*/
ShadowMap.prototype.debugCreateRenderStates = function() {
createRenderStates(this);
};
defineProperties(ShadowMap.prototype, {
/**
* Determines if the shadow map will be shown.
*
* @memberof ShadowMap.prototype
* @type {Boolean}
* @default true
*/
enabled : {
get : function() {
return this._enabled;
},
set : function(value) {
this.dirty = this._enabled !== value;
this._enabled = value;
}
},
/**
* Determines if soft shadows are enabled. Uses pcf filtering which requires more texture reads and may hurt performance.
*
* @memberof ShadowMap.prototype
* @type {Boolean}
* @default false
*/
softShadows : {
get : function() {
return this._softShadows;
},
set : function(value) {
this.dirty = this._softShadows !== value;
this._softShadows = value;
}
},
/**
* The width and height, in pixels, of each shadow map.
*
* @memberof ShadowMap.prototype
* @type {Number}
* @default 2048
*/
size : {
get : function() {
return this._size;
},
set : function(value) {
resize(this, value);
}
},
/**
* Whether the shadow map is out of view of the scene camera.
*
* @memberof ShadowMap.prototype
* @type {Boolean}
* @readonly
* @private
*/
outOfView : {
get : function() {
return this._outOfView;
}
},
/**
* The culling volume of the shadow frustum.
*
* @memberof ShadowMap.prototype
* @type {CullingVolume}
* @readonly
* @private
*/
shadowMapCullingVolume : {
get : function() {
return this._shadowMapCullingVolume;
}
},
/**
* The passes used for rendering shadows. Each face of a point light or each cascade for a cascaded shadow map is a separate pass.
*
* @memberof ShadowMap.prototype
* @type {ShadowPass[]}
* @readonly
* @private
*/
passes : {
get : function() {
return this._passes;
}
},
/**
* Whether the light source is a point light.
*
* @memberof ShadowMap.prototype
* @type {Boolean}
* @readonly
* @private
*/
isPointLight : {
get : function() {
return this._isPointLight;
}
},
/**
* Debug option for visualizing the cascades by color.
*
* @memberof ShadowMap.prototype
* @type {Boolean}
* @default false
* @private
*/
debugCascadeColors : {
get : function() {
return this._debugCascadeColors;
},
set : function(value) {
this.dirty = this._debugCascadeColors !== value;
this._debugCascadeColors = value;
}
}
});
function destroyFramebuffer(shadowMap) {
var length = shadowMap._passes.length;
for (var i = 0; i < length; ++i) {
var pass = shadowMap._passes[i];
var framebuffer = pass.framebuffer;
if (defined(framebuffer) && !framebuffer.isDestroyed()) {
framebuffer.destroy();
}
pass.framebuffer = undefined;
}
// Destroy the framebuffer attachments
shadowMap._depthAttachment = shadowMap._depthAttachment && shadowMap._depthAttachment.destroy();
shadowMap._colorAttachment = shadowMap._colorAttachment && shadowMap._colorAttachment.destroy();
}
function createSampler() {
return new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
minificationFilter : TextureMinificationFilter.NEAREST,
magnificationFilter : TextureMagnificationFilter.NEAREST
});
}
function createFramebufferColor(shadowMap, context) {
var depthRenderbuffer = new Renderbuffer({
context : context,
width : shadowMap._textureSize.x,
height : shadowMap._textureSize.y,
format : RenderbufferFormat.DEPTH_COMPONENT16
});
var colorTexture = new Texture({
context : context,
width : shadowMap._textureSize.x,
height : shadowMap._textureSize.y,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
sampler : createSampler()
});
var framebuffer = new Framebuffer({
context : context,
depthRenderbuffer : depthRenderbuffer,
colorTextures : [colorTexture],
destroyAttachments : false
});
var length = shadowMap._passes.length;
for (var i = 0; i < length; ++i) {
var pass = shadowMap._passes[i];
pass.framebuffer = framebuffer;
pass.passState.framebuffer = framebuffer;
}
shadowMap._shadowMapTexture = colorTexture;
shadowMap._depthAttachment = depthRenderbuffer;
shadowMap._colorAttachment = colorTexture;
}
function createFramebufferDepth(shadowMap, context) {
var depthStencilTexture = new Texture({
context : context,
width : shadowMap._textureSize.x,
height : shadowMap._textureSize.y,
pixelFormat : PixelFormat.DEPTH_STENCIL,
pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8,
sampler : createSampler()
});
var framebuffer = new Framebuffer({
context : context,
depthStencilTexture : depthStencilTexture,
destroyAttachments : false
});
var length = shadowMap._passes.length;
for (var i = 0; i < length; ++i) {
var pass = shadowMap._passes[i];
pass.framebuffer = framebuffer;
pass.passState.framebuffer = framebuffer;
}
shadowMap._shadowMapTexture = depthStencilTexture;
shadowMap._depthAttachment = depthStencilTexture;
}
function createFramebufferCube(shadowMap, context) {
var depthRenderbuffer = new Renderbuffer({
context : context,
width : shadowMap._textureSize.x,
height : shadowMap._textureSize.y,
format : RenderbufferFormat.DEPTH_COMPONENT16
});
var cubeMap = new CubeMap({
context : context,
width : shadowMap._textureSize.x,
height : shadowMap._textureSize.y,
pixelFormat : PixelFormat.RGBA,
pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
sampler : createSampler()
});
var faces = [cubeMap.negativeX, cubeMap.negativeY, cubeMap.negativeZ, cubeMap.positiveX, cubeMap.positiveY, cubeMap.positiveZ];
for (var i = 0; i < 6; ++i) {
var framebuffer = new Framebuffer({
context : context,
depthRenderbuffer : depthRenderbuffer,
colorTextures : [faces[i]],
destroyAttachments : false
});
var pass = shadowMap._passes[i];
pass.framebuffer = framebuffer;
pass.passState.framebuffer = framebuffer;
}
shadowMap._shadowMapTexture = cubeMap;
shadowMap._depthAttachment = depthRenderbuffer;
shadowMap._colorAttachment = cubeMap;
}
function createFramebuffer(shadowMap, context) {
if (shadowMap._isPointLight) {
createFramebufferCube(shadowMap, context);
} else if (shadowMap._usesDepthTexture) {
createFramebufferDepth(shadowMap, context);
} else {
createFramebufferColor(shadowMap, context);
}
}
function checkFramebuffer(shadowMap, context) {
// Attempt to make an FBO with only a depth texture. If it fails, fallback to a color texture.
if (shadowMap._usesDepthTexture && (shadowMap._passes[0].framebuffer.status !== WebGLConstants.FRAMEBUFFER_COMPLETE)) {
shadowMap._usesDepthTexture = false;
createRenderStates(shadowMap);
destroyFramebuffer(shadowMap);
createFramebuffer(shadowMap, context);
}
}
function updateFramebuffer(shadowMap, context) {
if (!defined(shadowMap._passes[0].framebuffer) || (shadowMap._shadowMapTexture.width !== shadowMap._textureSize.x)) {
destroyFramebuffer(shadowMap);
createFramebuffer(shadowMap, context);
checkFramebuffer(shadowMap, context);
clearFramebuffer(shadowMap, context);
}
}
function clearFramebuffer(shadowMap, context, shadowPass) {
shadowPass = defaultValue(shadowPass, 0);
if (shadowMap._isPointLight || (shadowPass === 0)) {
shadowMap._clearCommand.framebuffer = shadowMap._passes[shadowPass].framebuffer;
shadowMap._clearCommand.execute(context, shadowMap._clearPassState);
}
}
function resize(shadowMap, size) {
shadowMap._size = size;
var passes = shadowMap._passes;
var numberOfPasses = passes.length;
var textureSize = shadowMap._textureSize;
if (shadowMap._isPointLight) {
size = (ContextLimits.maximumCubeMapSize >= size) ? size : ContextLimits.maximumCubeMapSize;
textureSize.x = size;
textureSize.y = size;
var faceViewport = new BoundingRectangle(0, 0, size, size);
passes[0].passState.viewport = faceViewport;
passes[1].passState.viewport = faceViewport;
passes[2].passState.viewport = faceViewport;
passes[3].passState.viewport = faceViewport;
passes[4].passState.viewport = faceViewport;
passes[5].passState.viewport = faceViewport;
} else if (numberOfPasses === 1) {
// +----+
// | 1 |
// +----+
size = (ContextLimits.maximumTextureSize >= size) ? size : ContextLimits.maximumTextureSize;
textureSize.x = size;
textureSize.y = size;
passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
} else if (numberOfPasses === 4) {
// +----+----+
// | 3 | 4 |
// +----+----+
// | 1 | 2 |
// +----+----+
size = (ContextLimits.maximumTextureSize >= size * 2) ? size : ContextLimits.maximumTextureSize / 2;
textureSize.x = size * 2;
textureSize.y = size * 2;
passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
passes[1].passState.viewport = new BoundingRectangle(size, 0, size, size);
passes[2].passState.viewport = new BoundingRectangle(0, size, size, size);
passes[3].passState.viewport = new BoundingRectangle(size, size, size, size);
}
// Update clear pass state
shadowMap._clearPassState.viewport = new BoundingRectangle(0, 0, textureSize.x, textureSize.y);
// Transforms shadow coordinates [0, 1] into the pass's region of the texture
for (var i = 0; i < numberOfPasses; ++i) {
var pass = passes[i];
var viewport = pass.passState.viewport;
var biasX = viewport.x / textureSize.x;
var biasY = viewport.y / textureSize.y;
var scaleX = viewport.width / textureSize.x;
var scaleY = viewport.height / textureSize.y;
pass.textureOffsets = new Matrix4(scaleX, 0.0, 0.0, biasX, 0.0, scaleY, 0.0, biasY, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
}
}
var scratchViewport = new BoundingRectangle();
function createDebugShadowViewCommand(shadowMap, context) {
var fs;
if (shadowMap._isPointLight) {
fs = 'uniform samplerCube shadowMap_textureCube; \n' +
'varying vec2 v_textureCoordinates; \n' +
'void main() \n' +
'{ \n' +
' vec2 uv = v_textureCoordinates; \n' +
' vec3 dir; \n' +
' \n' +
' if (uv.y < 0.5) \n' +
' { \n' +
' if (uv.x < 0.333) \n' +
' { \n' +
' dir.x = -1.0; \n' +
' dir.y = uv.x * 6.0 - 1.0; \n' +
' dir.z = uv.y * 4.0 - 1.0; \n' +
' } \n' +
' else if (uv.x < 0.666) \n' +
' { \n' +
' dir.y = -1.0; \n' +
' dir.x = uv.x * 6.0 - 3.0; \n' +
' dir.z = uv.y * 4.0 - 1.0; \n' +
' } \n' +
' else \n' +
' { \n' +
' dir.z = -1.0; \n' +
' dir.x = uv.x * 6.0 - 5.0; \n' +
' dir.y = uv.y * 4.0 - 1.0; \n' +
' } \n' +
' } \n' +
' else \n' +
' { \n' +
' if (uv.x < 0.333) \n' +
' { \n' +
' dir.x = 1.0; \n' +
' dir.y = uv.x * 6.0 - 1.0; \n' +
' dir.z = uv.y * 4.0 - 3.0; \n' +
' } \n' +
' else if (uv.x < 0.666) \n' +
' { \n' +
' dir.y = 1.0; \n' +
' dir.x = uv.x * 6.0 - 3.0; \n' +
' dir.z = uv.y * 4.0 - 3.0; \n' +
' } \n' +
' else \n' +
' { \n' +
' dir.z = 1.0; \n' +
' dir.x = uv.x * 6.0 - 5.0; \n' +
' dir.y = uv.y * 4.0 - 3.0; \n' +
' } \n' +
' } \n' +
' \n' +
' float shadow = czm_unpackDepth(textureCube(shadowMap_textureCube, dir)); \n' +
' gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
'} \n';
} else {
fs = 'uniform sampler2D shadowMap_texture; \n' +
'varying vec2 v_textureCoordinates; \n' +
'void main() \n' +
'{ \n' +
(shadowMap._usesDepthTexture ?
' float shadow = texture2D(shadowMap_texture, v_textureCoordinates).r; \n' :
' float shadow = czm_unpackDepth(texture2D(shadowMap_texture, v_textureCoordinates)); \n') +
' gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
'} \n';
}
var drawCommand = context.createViewportQuadCommand(fs, {
uniformMap : {
shadowMap_texture : function() {
return shadowMap._shadowMapTexture;
},
shadowMap_textureCube : function() {
return shadowMap._shadowMapTexture;
}
}
});
drawCommand.pass = Pass.OVERLAY;
return drawCommand;
}
function updateDebugShadowViewCommand(shadowMap, frameState) {
// Draws the shadow map on the bottom-right corner of the screen
var context = frameState.context;
var screenWidth = frameState.context.drawingBufferWidth;
var screenHeight = frameState.context.drawingBufferHeight;
var size = Math.min(screenWidth, screenHeight) * 0.3;
var viewport = scratchViewport;
viewport.x = screenWidth - size;
viewport.y = 0;
viewport.width = size;
viewport.height = size;
var debugCommand = shadowMap._debugShadowViewCommand;
if (!defined(debugCommand)) {
debugCommand = createDebugShadowViewCommand(shadowMap, context);
shadowMap._debugShadowViewCommand = debugCommand;
}
// Get a new RenderState for the updated viewport size
if (!defined(debugCommand.renderState) || !BoundingRectangle.equals(debugCommand.renderState.viewport, viewport)) {
debugCommand.renderState = RenderState.fromCache({
viewport : BoundingRectangle.clone(viewport)
});
}
frameState.commandList.push(shadowMap._debugShadowViewCommand);
}
var frustumCornersNDC = new Array(8);
frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0);
frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0);
frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0);
frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0);
frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);
var scratchMatrix = new Matrix4();
var scratchFrustumCorners = new Array(8);
for (var i = 0; i < 8; ++i) {
scratchFrustumCorners[i] = new Cartesian4();
}
function createDebugPointLight(modelMatrix, color) {
var box = new GeometryInstance({
geometry : new BoxOutlineGeometry({
minimum : new Cartesian3(-0.5, -0.5, -0.5),
maximum : new Cartesian3(0.5, 0.5, 0.5)
}),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(color)
}
});
var sphere = new GeometryInstance({
geometry : new SphereOutlineGeometry({
radius : 0.5
}),
attributes : {
color : ColorGeometryInstanceAttribute.fromColor(color)
}
});
return new Primitive({
geometryInstances : [box, sphere],
appearance : new PerInstanceColorAppearance({
translucent : false,
flat : true
}),
asynchronous : false,
modelMatrix : modelMatrix
});
}
var debugOutlineColors = [Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA];
var scratchScale = new Cartesian3();
function applyDebugSettings(shadowMap, frameState) {
updateDebugShadowViewCommand(shadowMap, frameState);
var enterFreezeFrame = shadowMap.debugFreezeFrame && !shadowMap._debugFreezeFrame;
shadowMap._debugFreezeFrame = shadowMap.debugFreezeFrame;
// Draw scene camera in freeze frame mode
if (shadowMap.debugFreezeFrame) {
if (enterFreezeFrame) {
// Recreate debug camera when entering freeze frame mode
shadowMap._debugCameraFrustum = shadowMap._debugCameraFrustum && shadowMap._debugCameraFrustum.destroy();
shadowMap._debugCameraFrustum = new DebugCameraPrimitive({
camera : shadowMap._sceneCamera,
color : Color.CYAN,
updateOnChange : false
});
}
shadowMap._debugCameraFrustum.update(frameState);
}
if (shadowMap._cascadesEnabled) {
// Draw cascades only in freeze frame mode
if (shadowMap.debugFreezeFrame) {
if (enterFreezeFrame) {
// Recreate debug frustum when entering freeze frame mode
shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
shadowMap._debugLightFrustum = new DebugCameraPrimitive({
camera : shadowMap._shadowMapCamera,
color : Color.YELLOW,
updateOnChange : false
});
}
shadowMap._debugLightFrustum.update(frameState);
for (var i = 0; i < shadowMap._numberOfCascades; ++i) {
if (enterFreezeFrame) {
// Recreate debug frustum when entering freeze frame mode
shadowMap._debugCascadeFrustums[i] = shadowMap._debugCascadeFrustums[i] && shadowMap._debugCascadeFrustums[i].destroy();
shadowMap._debugCascadeFrustums[i] = new DebugCameraPrimitive({
camera : shadowMap._passes[i].camera,
color : debugOutlineColors[i],
updateOnChange : false
});
}
shadowMap._debugCascadeFrustums[i].update(frameState);
}
}
} else if (shadowMap._isPointLight) {
if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
var translation = shadowMap._shadowMapCamera.positionWC;
var rotation = Quaternion.IDENTITY;
var uniformScale = shadowMap._pointLightRadius * 2.0;
var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);
shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
shadowMap._debugLightFrustum = createDebugPointLight(modelMatrix, Color.YELLOW);
}
shadowMap._debugLightFrustum.update(frameState);
} else {
if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
shadowMap._debugLightFrustum = new DebugCameraPrimitive({
camera : shadowMap._shadowMapCamera,
color : Color.YELLOW,
updateOnChange : false
});
}
shadowMap._debugLightFrustum.update(frameState);
}
}
function ShadowMapCamera() {
this.viewMatrix = new Matrix4();
this.inverseViewMatrix = new Matrix4();
this.frustum = undefined;
this.positionCartographic = new Cartographic();
this.positionWC = new Cartesian3();
this.directionWC = Cartesian3.clone(Cartesian3.UNIT_Z);
this.upWC = Cartesian3.clone(Cartesian3.UNIT_Y);
this.rightWC = Cartesian3.clone(Cartesian3.UNIT_X);
this.viewProjectionMatrix = new Matrix4();
}
ShadowMapCamera.prototype.clone = function(camera) {
Matrix4.clone(camera.viewMatrix, this.viewMatrix);
Matrix4.clone(camera.inverseViewMatrix, this.inverseViewMatrix);
this.frustum = camera.frustum.clone(this.frustum);
Cartographic.clone(camera.positionCartographic, this.positionCartographic);
Cartesian3.clone(camera.positionWC, this.positionWC);
Cartesian3.clone(camera.directionWC, this.directionWC);
Cartesian3.clone(camera.upWC, this.upWC);
Cartesian3.clone(camera.rightWC, this.rightWC);
};
// Converts from NDC space to texture space
var scaleBiasMatrix = new Matrix4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0);
ShadowMapCamera.prototype.getViewProjection = function() {
var view = this.viewMatrix;
var projection = this.frustum.projectionMatrix;
Matrix4.multiply(projection, view, this.viewProjectionMatrix);
Matrix4.multiply(scaleBiasMatrix, this.viewProjectionMatrix, this.viewProjectionMatrix);
return this.viewProjectionMatrix;
};
var scratchSplits = new Array(5);
var scratchFrustum = new PerspectiveFrustum();
var scratchCascadeDistances = new Array(4);
function computeCascades(shadowMap, frameState) {
var shadowMapCamera = shadowMap._shadowMapCamera;
var sceneCamera = shadowMap._sceneCamera;
var cameraNear = sceneCamera.frustum.near;
var cameraFar = sceneCamera.frustum.far;
var numberOfCascades = shadowMap._numberOfCascades;
// Split cascades. Use a mix of linear and log splits.
var i;
var range = cameraFar - cameraNear;
var ratio = cameraFar / cameraNear;
var lambda = 0.9;
var clampCascadeDistances = false;
// When the camera is close to a relatively small model, provide more detail in the closer cascades.
// If the camera is near or inside a large model, such as the root tile of a city, then use the default values.
// To get the most accurate cascade splits we would need to find the min and max values from the depth texture.
if (frameState.shadowHints.closestObjectSize < 200.0) {
clampCascadeDistances = true;
lambda = 0.9;
}
var cascadeDistances = scratchCascadeDistances;
var splits = scratchSplits;
splits[0] = cameraNear;
splits[numberOfCascades] = cameraFar;
// Find initial splits
for (i = 0; i < numberOfCascades; ++i) {
var p = (i + 1) / numberOfCascades;
var logScale = cameraNear * Math.pow(ratio, p);
var uniformScale = cameraNear + range * p;
var split = CesiumMath.lerp(uniformScale, logScale, lambda);
splits[i + 1] = split;
cascadeDistances[i] = split - splits[i];
}
if (clampCascadeDistances) {
// Clamp each cascade to its maximum distance
for (i = 0; i < numberOfCascades; ++i) {
cascadeDistances[i] = Math.min(cascadeDistances[i], shadowMap._maximumCascadeDistances[i]);
}
// Recompute splits
var distance = splits[0];
for (i = 0; i < numberOfCascades - 1; ++i) {
distance += cascadeDistances[i];
splits[i + 1] = distance;
}
}
Cartesian4.unpack(splits, 0, shadowMap._cascadeSplits[0]);
Cartesian4.unpack(splits, 1, shadowMap._cascadeSplits[1]);
Cartesian4.unpack(cascadeDistances, 0, shadowMap._cascadeDistances);
var shadowFrustum = shadowMapCamera.frustum;
var left = shadowFrustum.left;
var right = shadowFrustum.right;
var bottom = shadowFrustum.bottom;
var top = shadowFrustum.top;
var near = shadowFrustum.near;
var far = shadowFrustum.far;
var position = shadowMapCamera.positionWC;
var direction = shadowMapCamera.directionWC;
var up = shadowMapCamera.upWC;
var cascadeSubFrustum = sceneCamera.frustum.clone(scratchFrustum);
var shadowViewProjection = shadowMapCamera.getViewProjection();
for (i = 0; i < numberOfCascades; ++i) {
// Find the bounding box of the camera sub-frustum in shadow map texture space
cascadeSubFrustum.near = splits[i];
cascadeSubFrustum.far = splits[i + 1];
var viewProjection = Matrix4.multiply(cascadeSubFrustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
var shadowMapMatrix = Matrix4.multiply(shadowViewProjection, inverseViewProjection, scratchMatrix);
// Project each corner from camera NDC space to shadow map texture space. Min and max will be from 0 to 1.
var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);
for (var k = 0; k < 8; ++k) {
var corner = Cartesian4.clone(frustumCornersNDC[k], scratchFrustumCorners[k]);
Matrix4.multiplyByVector(shadowMapMatrix, corner, corner);
Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
Cartesian3.minimumByComponent(corner, min, min);
Cartesian3.maximumByComponent(corner, max, max);
}
// Limit light-space coordinates to the [0, 1] range
min.x = Math.max(min.x, 0.0);
min.y = Math.max(min.y, 0.0);
min.z = 0.0; // Always start cascade frustum at the top of the light frustum to capture objects in the light's path
max.x = Math.min(max.x, 1.0);
max.y = Math.min(max.y, 1.0);
max.z = Math.min(max.z, 1.0);
var pass = shadowMap._passes[i];
var cascadeCamera = pass.camera;
cascadeCamera.clone(shadowMapCamera); // PERFORMANCE_IDEA : could do a shallow clone for all properties except the frustum
var frustum = cascadeCamera.frustum;
frustum.left = left + min.x * (right - left);
frustum.right = left + max.x * (right - left);
frustum.bottom = bottom + min.y * (top - bottom);
frustum.top = bottom + max.y * (top - bottom);
frustum.near = near + min.z * (far - near);
frustum.far = near + max.z * (far - near);
pass.cullingVolume = cascadeCamera.frustum.computeCullingVolume(position, direction, up);
// Transforms from eye space to the cascade's texture space
var cascadeMatrix = shadowMap._cascadeMatrices[i];
Matrix4.multiply(cascadeCamera.getViewProjection(), sceneCamera.inverseViewMatrix, cascadeMatrix);
Matrix4.multiply(pass.textureOffsets, cascadeMatrix, cascadeMatrix);
}
}
var scratchLightView = new Matrix4();
var scratchRight = new Cartesian3();
var scratchUp = new Cartesian3();
var scratchMin = new Cartesian3();
var scratchMax = new Cartesian3();
var scratchTranslation = new Cartesian3();
function fitShadowMapToScene(shadowMap, frameState) {
var shadowMapCamera = shadowMap._shadowMapCamera;
var sceneCamera = shadowMap._sceneCamera;
// 1. First find a tight bounding box in light space that contains the entire camera frustum.
var viewProjection = Matrix4.multiply(sceneCamera.frustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
// Start to construct the light view matrix. Set translation later once the bounding box is found.
var lightDir = shadowMapCamera.directionWC;
var lightUp = sceneCamera.directionWC; // Align shadows to the camera view.
var lightRight = Cartesian3.cross(lightDir, lightUp, scratchRight);
lightUp = Cartesian3.cross(lightRight, lightDir, scratchUp); // Recalculate up now that right is derived
Cartesian3.normalize(lightUp, lightUp);
Cartesian3.normalize(lightRight, lightRight);
var lightPosition = Cartesian3.fromElements(0.0, 0.0, 0.0, scratchTranslation);
var lightView = Matrix4.computeView(lightPosition, lightDir, lightUp, lightRight, scratchLightView);
var cameraToLight = Matrix4.multiply(lightView, inverseViewProjection, scratchMatrix);
// Project each corner from NDC space to light view space, and calculate a min and max in light view space
var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);
for (var i = 0; i < 8; ++i) {
var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);
Matrix4.multiplyByVector(cameraToLight, corner, corner);
Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
Cartesian3.minimumByComponent(corner, min, min);
Cartesian3.maximumByComponent(corner, max, max);
}
// 2. Set bounding box back to include objects in the light's view
max.z += 1000.0; // Note: in light space, a positive number is behind the camera
min.z -= 10.0; // Extend the shadow volume forward slightly to avoid problems right at the edge
// 3. Adjust light view matrix so that it is centered on the bounding volume
var translation = scratchTranslation;
translation.x = -(0.5 * (min.x + max.x));
translation.y = -(0.5 * (min.y + max.y));
translation.z = -max.z;
var translationMatrix = Matrix4.fromTranslation(translation, scratchMatrix);
lightView = Matrix4.multiply(translationMatrix, lightView, lightView);
// 4. Create an orthographic frustum that covers the bounding box extents
var halfWidth = 0.5 * (max.x - min.x);
var halfHeight = 0.5 * (max.y - min.y);
var depth = max.z - min.z;
var frustum = shadowMapCamera.frustum;
frustum.left = -halfWidth;
frustum.right = halfWidth;
frustum.bottom = -halfHeight;
frustum.top = halfHeight;
frustum.near = 0.01;
frustum.far = depth;
// 5. Update the shadow map camera
Matrix4.clone(lightView, shadowMapCamera.viewMatrix);
Matrix4.inverse(lightView, shadowMapCamera.inverseViewMatrix);
Matrix4.getTranslation(shadowMapCamera.inverseViewMatrix, shadowMapCamera.positionWC);
frameState.mapProjection.ellipsoid.cartesianToCartographic(shadowMapCamera.positionWC, shadowMapCamera.positionCartographic);
Cartesian3.clone(lightDir, shadowMapCamera.directionWC);
Cartesian3.clone(lightUp, shadowMapCamera.upWC);
Cartesian3.clone(lightRight, shadowMapCamera.rightWC);
}
var directions = [
new Cartesian3(-1.0, 0.0, 0.0),
new Cartesian3(0.0, -1.0, 0.0),
new Cartesian3(0.0, 0.0, -1.0),
new Cartesian3(1.0, 0.0, 0.0),
new Cartesian3(0.0, 1.0, 0.0),
new Cartesian3(0.0, 0.0, 1.0)
];
var ups = [
new Cartesian3(0.0, -1.0, 0.0),
new Cartesian3(0.0, 0.0, -1.0),
new Cartesian3(0.0, -1.0, 0.0),
new Cartesian3(0.0, -1.0, 0.0),
new Cartesian3(0.0, 0.0, 1.0),
new Cartesian3(0.0, -1.0, 0.0)
];
var rights = [
new Cartesian3(0.0, 0.0, 1.0),
new Cartesian3(1.0, 0.0, 0.0),
new Cartesian3(-1.0, 0.0, 0.0),
new Cartesian3(0.0, 0.0, -1.0),
new Cartesian3(1.0, 0.0, 0.0),
new Cartesian3(1.0, 0.0, 0.0)
];
function computeOmnidirectional(shadowMap, frameState) {
// All sides share the same frustum
var frustum = new PerspectiveFrustum();
frustum.fov = CesiumMath.PI_OVER_TWO;
frustum.near = 1.0;
frustum.far = shadowMap._pointLightRadius;
frustum.aspectRatio = 1.0;
for (var i = 0; i < 6; ++i) {
var camera = shadowMap._passes[i].camera;
camera.positionWC = shadowMap._shadowMapCamera.positionWC;
camera.positionCartographic = frameState.mapProjection.ellipsoid.cartesianToCartographic(camera.positionWC, camera.positionCartographic);
camera.directionWC = directions[i];
camera.upWC = ups[i];
camera.rightWC = rights[i];
Matrix4.computeView(camera.positionWC, camera.directionWC, camera.upWC, camera.rightWC, camera.viewMatrix);
Matrix4.inverse(camera.viewMatrix, camera.inverseViewMatrix);
camera.frustum = frustum;
}
}
var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchBoundingSphere = new BoundingSphere();
var scratchCenter = scratchBoundingSphere.center;
function checkVisibility(shadowMap, frameState) {
var sceneCamera = shadowMap._sceneCamera;
var shadowMapCamera = shadowMap._shadowMapCamera;
var boundingSphere = scratchBoundingSphere;
// Check whether the shadow map is in view and needs to be updated
if (shadowMap._cascadesEnabled) {
// If the nearest shadow receiver is further than the shadow map's maximum distance then the shadow map is out of view.
if (sceneCamera.frustum.near >= shadowMap.maximumDistance) {
shadowMap._outOfView = true;
shadowMap._needsUpdate = false;
return;
}
// If the light source is below the horizon then the shadow map is out of view
var surfaceNormal = frameState.mapProjection.ellipsoid.geodeticSurfaceNormal(sceneCamera.positionWC, scratchCartesian1);
var lightDirection = Cartesian3.negate(shadowMapCamera.directionWC, scratchCartesian2);
var dot = Cartesian3.dot(surfaceNormal, lightDirection);
// Shadows start to fade out once the light gets closer to the horizon.
// At this point the globe uses vertex lighting alone to darken the surface.
var darknessAmount = CesiumMath.clamp(dot / 0.1, 0.0, 1.0);
shadowMap._darkness = CesiumMath.lerp(1.0, shadowMap.darkness, darknessAmount);
if (dot < 0.0) {
shadowMap._outOfView = true;
shadowMap._needsUpdate = false;
return;
}
// By default cascaded shadows need to update and are always in view
shadowMap._needsUpdate = true;
shadowMap._outOfView = false;
} else if (shadowMap._isPointLight) {
// Sphere-frustum intersection test
boundingSphere.center = shadowMapCamera.positionWC;
boundingSphere.radius = shadowMap._pointLightRadius;
shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
} else {
// Simplify frustum-frustum intersection test as a sphere-frustum test
var frustumRadius = shadowMapCamera.frustum.far / 2.0;
var frustumCenter = Cartesian3.add(shadowMapCamera.positionWC, Cartesian3.multiplyByScalar(shadowMapCamera.directionWC, frustumRadius, scratchCenter), scratchCenter);
boundingSphere.center = frustumCenter;
boundingSphere.radius = frustumRadius;
shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
}
}
function updateCameras(shadowMap, frameState) {
var camera = frameState.camera; // The actual camera in the scene
var lightCamera = shadowMap._lightCamera; // The external camera representing the light source
var sceneCamera = shadowMap._sceneCamera; // Clone of camera, with clamped near and far planes
var shadowMapCamera = shadowMap._shadowMapCamera; // Camera representing the shadow volume, initially cloned from lightCamera
// Clone light camera into the shadow map camera
if (shadowMap._cascadesEnabled) {
Cartesian3.clone(lightCamera.directionWC, shadowMapCamera.directionWC);
} else if (shadowMap._isPointLight) {
Cartesian3.clone(lightCamera.positionWC, shadowMapCamera.positionWC);
} else {
shadowMapCamera.clone(lightCamera);
}
// Get the light direction in eye coordinates
var lightDirection = shadowMap._lightDirectionEC;
Matrix4.multiplyByPointAsVector(camera.viewMatrix, shadowMapCamera.directionWC, lightDirection);
Cartesian3.normalize(lightDirection, lightDirection);
Cartesian3.negate(lightDirection, lightDirection);
// Get the light position in eye coordinates
Matrix4.multiplyByPoint(camera.viewMatrix, shadowMapCamera.positionWC, shadowMap._lightPositionEC);
shadowMap._lightPositionEC.w = shadowMap._pointLightRadius;
// Get the near and far of the scene camera
var near;
var far;
if (shadowMap._fitNearFar) {
// shadowFar can be very large, so limit to shadowMap.maximumDistance
// Push the far plane slightly further than the near plane to avoid degenerate frustum
near = Math.min(frameState.shadowHints.nearPlane, shadowMap.maximumDistance);
far = Math.min(frameState.shadowHints.farPlane, shadowMap.maximumDistance + 1.0);
} else {
near = camera.frustum.near;
far = shadowMap.maximumDistance;
}
shadowMap._sceneCamera = Camera.clone(camera, sceneCamera);
camera.frustum.clone(shadowMap._sceneCamera.frustum);
shadowMap._sceneCamera.frustum.near = near;
shadowMap._sceneCamera.frustum.far = far;
shadowMap._distance = far - near;
checkVisibility(shadowMap, frameState);
if (!shadowMap._outOfViewPrevious && shadowMap._outOfView) {
shadowMap._needsUpdate = true;
}
shadowMap._outOfViewPrevious = shadowMap._outOfView;
}
/**
* @private
*/
ShadowMap.prototype.update = function(frameState) {
updateCameras(this, frameState);
if (this._needsUpdate) {
updateFramebuffer(this, frameState.context);
if (this._isPointLight) {
computeOmnidirectional(this, frameState);
}
if (this._cascadesEnabled) {
fitShadowMapToScene(this, frameState);
if (this._numberOfCascades > 1) {
computeCascades(this, frameState);
}
}
if (!this._isPointLight) {
// Compute the culling volume
var shadowMapCamera = this._shadowMapCamera;
var position = shadowMapCamera.positionWC;
var direction = shadowMapCamera.directionWC;
var up = shadowMapCamera.upWC;
this._shadowMapCullingVolume = shadowMapCamera.frustum.computeCullingVolume(position, direction, up);
if (this._passes.length === 1) {
// Since there is only one pass, use the shadow map camera as the pass camera.
this._passes[0].camera.clone(shadowMapCamera);
}
} else {
this._shadowMapCullingVolume = CullingVolume.fromBoundingSphere(this._boundingSphere);
}
}
if (this._passes.length === 1) {
// Transforms from eye space to shadow texture space.
// Always requires an update since the scene camera constantly changes.
var inverseView = this._sceneCamera.inverseViewMatrix;
Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix);
}
if (this.debugShow) {
applyDebugSettings(this, frameState);
}
};
/**
* @private
*/
ShadowMap.prototype.updatePass = function(context, shadowPass) {
clearFramebuffer(this, context, shadowPass);
};
var scratchTexelStepSize = new Cartesian2();
function combineUniforms(shadowMap, uniforms, isTerrain) {
var bias = shadowMap._isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);
var mapUniforms = {
shadowMap_texture :function() {
return shadowMap._shadowMapTexture;
},
shadowMap_textureCube : function() {
return shadowMap._shadowMapTexture;
},
shadowMap_matrix : function() {
return shadowMap._shadowMapMatrix;
},
shadowMap_cascadeSplits : function() {
return shadowMap._cascadeSplits;
},
shadowMap_cascadeMatrices : function() {
return shadowMap._cascadeMatrices;
},
shadowMap_lightDirectionEC : function() {
return shadowMap._lightDirectionEC;
},
shadowMap_lightPositionEC : function() {
return shadowMap._lightPositionEC;
},
shadowMap_cascadeDistances : function() {
return shadowMap._cascadeDistances;
},
shadowMap_texelSizeDepthBiasAndNormalShadingSmooth : function() {
var texelStepSize = scratchTexelStepSize;
texelStepSize.x = 1.0 / shadowMap._textureSize.x;
texelStepSize.y = 1.0 / shadowMap._textureSize.y;
return Cartesian4.fromElements(texelStepSize.x, texelStepSize.y, bias.depthBias, bias.normalShadingSmooth, this.combinedUniforms1);
},
shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness : function() {
return Cartesian4.fromElements(bias.normalOffsetScale, shadowMap._distance, shadowMap.maximumDistance, shadowMap._darkness, this.combinedUniforms2);
},
combinedUniforms1 : new Cartesian4(),
combinedUniforms2 : new Cartesian4()
};
return combine(uniforms, mapUniforms, false);
}
function createCastDerivedCommand(shadowMap, shadowsDirty, command, context, oldShaderId, result) {
var castShader;
var castRenderState;
var castUniformMap;
if (defined(result)) {
castShader = result.shaderProgram;
castRenderState = result.renderState;
castUniformMap = result.uniformMap;
}
result = DrawCommand.shallowClone(command, result);
result.castShadows = true;
result.receiveShadows = false;
if (!defined(castShader) || oldShaderId !== command.shaderProgram.id || shadowsDirty) {
if (defined(castShader)) {
castShader.destroy();
}
var shaderProgram = command.shaderProgram;
var vertexShaderSource = shaderProgram.vertexShaderSource;
var fragmentShaderSource = shaderProgram.fragmentShaderSource;
var isTerrain = command.pass === Pass.GLOBE;
var isOpaque = command.pass !== Pass.TRANSLUCENT;
var isPointLight = shadowMap._isPointLight;
var usesDepthTexture= shadowMap._usesDepthTexture;
var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain);
var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, usesDepthTexture, isOpaque);
castShader = ShaderProgram.fromCache({
context : context,
vertexShaderSource : castVS,
fragmentShaderSource : castFS,
attributeLocations : shaderProgram._attributeLocations
});
castRenderState = shadowMap._primitiveRenderState;
if (isPointLight) {
castRenderState = shadowMap._pointRenderState;
} else if (isTerrain) {
castRenderState = shadowMap._terrainRenderState;
}
// Modify the render state for commands that do not use back-face culling, e.g. flat textured walls
var cullEnabled = command.renderState.cull.enabled;
if (!cullEnabled) {
castRenderState = clone(castRenderState, false);
castRenderState.cull.enabled = false;
castRenderState = RenderState.fromCache(castRenderState);
}
castUniformMap = combineUniforms(shadowMap, command.uniformMap, isTerrain);
}
result.shaderProgram = castShader;
result.renderState = castRenderState;
result.uniformMap = castUniformMap;
return result;
}
ShadowMap.createDerivedCommands = function(shadowMaps, lightShadowMaps, command, shadowsDirty, context, result) {
if (!defined(result)) {
result = {};
}
var lightShadowMapsEnabled = (lightShadowMaps.length > 0);
var shaderProgram = command.shaderProgram;
var vertexShaderSource = shaderProgram.vertexShaderSource;
var fragmentShaderSource = shaderProgram.fragmentShaderSource;
var isTerrain = command.pass === Pass.GLOBE;
var hasTerrainNormal = false;
if (isTerrain) {
hasTerrainNormal = command.owner.data.pickTerrain.mesh.encoding.hasVertexNormals;
}
if (command.castShadows) {
var castCommands = result.castCommands;
if (!defined(castCommands)) {
castCommands = result.castCommands = [];
}
var oldShaderId = result.castShaderProgramId;
var shadowMapLength = shadowMaps.length;
castCommands.length = shadowMapLength;
for (var i = 0; i < shadowMapLength; ++i) {
castCommands[i] = createCastDerivedCommand(shadowMaps[i], shadowsDirty, command, context, oldShaderId, castCommands[i]);
}
result.castShaderProgramId = command.shaderProgram.id;
}
if (command.receiveShadows && lightShadowMapsEnabled) {
// Only generate a receiveCommand if there is a shadow map originating from a light source.
var receiveShader;
var receiveUniformMap;
if (defined(result.receiveCommand)) {
receiveShader = result.receiveCommand.shaderProgram;
receiveUniformMap = result.receiveCommand.uniformMap;
}
result.receiveCommand = DrawCommand.shallowClone(command, result.receiveCommand);
result.castShadows = false;
result.receiveShadows = true;
// If castShadows changed, recompile the receive shadows shader. The normal shading technique simulates
// self-shadowing so it should be turned off if castShadows is false.
var castShadowsDirty = result.receiveShaderCastShadows !== command.castShadows;
var shaderDirty = result.receiveShaderProgramId !== command.shaderProgram.id;
if (!defined(receiveShader) || shaderDirty || shadowsDirty || castShadowsDirty) {
if (defined(receiveShader)) {
receiveShader.destroy();
}
var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal);
var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);
receiveShader = ShaderProgram.fromCache({
context : context,
vertexShaderSource : receiveVS,
fragmentShaderSource : receiveFS,
attributeLocations : shaderProgram._attributeLocations
});
receiveUniformMap = combineUniforms(lightShadowMaps[0], command.uniformMap, isTerrain);
}
result.receiveCommand.shaderProgram = receiveShader;
result.receiveCommand.uniformMap = receiveUniformMap;
result.receiveShaderProgramId = command.shaderProgram.id;
result.receiveShaderCastShadows = command.castShadows;
}
return result;
};
/**
* @private
*/
ShadowMap.prototype.isDestroyed = function() {
return false;
};
/**
* @private
*/
ShadowMap.prototype.destroy = function() {
destroyFramebuffer(this);
this._debugLightFrustum = this._debugLightFrustum && this._debugLightFrustum.destroy();
this._debugCameraFrustum = this._debugCameraFrustum && this._debugCameraFrustum.destroy();
this._debugShadowViewCommand = this._debugShadowViewCommand && this._debugShadowViewCommand.shaderProgram && this._debugShadowViewCommand.shaderProgram.destroy();
for (var i = 0; i < this._numberOfCascades; ++i) {
this._debugCascadeFrustums[i] = this._debugCascadeFrustums[i] && this._debugCascadeFrustums[i].destroy();
}
return destroyObject(this);
};
return ShadowMap;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/AdditiveBlend',[],function() {
'use strict';
return "uniform sampler2D u_texture0;\n\
uniform sampler2D u_texture1;\n\
uniform vec2 u_center;\n\
uniform float u_radius;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
vec4 color0 = texture2D(u_texture0, v_textureCoordinates);\n\
vec4 color1 = texture2D(u_texture1, v_textureCoordinates);\n\
float x = length(gl_FragCoord.xy - u_center) / u_radius;\n\
float t = smoothstep(0.5, 0.8, x);\n\
gl_FragColor = mix(color0 + color1, color0, t);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/BrightPass',[],function() {
'use strict';
return "uniform sampler2D u_texture;\n\
uniform float u_avgLuminance;\n\
uniform float u_threshold;\n\
uniform float u_offset;\n\
varying vec2 v_textureCoordinates;\n\
float key(float avg)\n\
{\n\
float guess = 1.5 - (1.5 / (avg * 0.1 + 1.0));\n\
return max(0.0, guess) + 0.1;\n\
}\n\
void main()\n\
{\n\
vec4 color = texture2D(u_texture, v_textureCoordinates);\n\
vec3 xyz = czm_RGBToXYZ(color.rgb);\n\
float luminance = xyz.r;\n\
float scaledLum = key(u_avgLuminance) * luminance / u_avgLuminance;\n\
float brightLum = max(scaledLum - u_threshold, 0.0);\n\
float brightness = brightLum / (u_offset + brightLum);\n\
xyz.r = brightness;\n\
gl_FragColor = vec4(czm_XYZToRGB(xyz), 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/GaussianBlur1D',[],function() {
'use strict';
return "#define SAMPLES 8\n\
uniform float delta;\n\
uniform float sigma;\n\
uniform float direction;\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_step;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
vec2 st = v_textureCoordinates;\n\
vec2 dir = vec2(1.0 - direction, direction);\n\
vec3 g;\n\
g.x = 1.0 / (sqrt(czm_twoPi) * sigma);\n\
g.y = exp((-0.5 * delta * delta) / (sigma * sigma));\n\
g.z = g.y * g.y;\n\
vec4 result = texture2D(u_texture, st) * g.x;\n\
for (int i = 1; i < SAMPLES; ++i)\n\
{\n\
g.xy *= g.yz;\n\
vec2 offset = float(i) * dir * u_step;\n\
result += texture2D(u_texture, st - offset) * g.x;\n\
result += texture2D(u_texture, st + offset) * g.x;\n\
}\n\
gl_FragColor = result;\n\
}\n\
";
});
/*global define*/
define('Scene/SunPostProcess',[
'../Core/BoundingRectangle',
'../Core/Cartesian2',
'../Core/Cartesian4',
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/Math',
'../Core/Matrix4',
'../Core/PixelFormat',
'../Core/Transforms',
'../Renderer/ClearCommand',
'../Renderer/Framebuffer',
'../Renderer/PassState',
'../Renderer/PixelDatatype',
'../Renderer/Renderbuffer',
'../Renderer/RenderbufferFormat',
'../Renderer/RenderState',
'../Renderer/Texture',
'../Shaders/PostProcessFilters/AdditiveBlend',
'../Shaders/PostProcessFilters/BrightPass',
'../Shaders/PostProcessFilters/GaussianBlur1D',
'../Shaders/PostProcessFilters/PassThrough'
], function(
BoundingRectangle,
Cartesian2,
Cartesian4,
Color,
defaultValue,
defined,
destroyObject,
CesiumMath,
Matrix4,
PixelFormat,
Transforms,
ClearCommand,
Framebuffer,
PassState,
PixelDatatype,
Renderbuffer,
RenderbufferFormat,
RenderState,
Texture,
AdditiveBlend,
BrightPass,
GaussianBlur1D,
PassThrough) {
'use strict';
function SunPostProcess() {
this._fbo = undefined;
this._downSampleFBO1 = undefined;
this._downSampleFBO2 = undefined;
this._clearFBO1Command = undefined;
this._clearFBO2Command = undefined;
this._downSampleCommand = undefined;
this._brightPassCommand = undefined;
this._blurXCommand = undefined;
this._blurYCommand = undefined;
this._blendCommand = undefined;
this._fullScreenCommand = undefined;
this._downSamplePassState = new PassState();
this._downSamplePassState.scissorTest = {
enable : true,
rectangle : new BoundingRectangle()
};
this._upSamplePassState = new PassState();
this._upSamplePassState.scissorTest = {
enabled : true,
rectangle : new BoundingRectangle()
};
this._uCenter = new Cartesian2();
this._uRadius = undefined;
this._blurStep = new Cartesian2();
}
SunPostProcess.prototype.clear = function(context, color) {
var clear = this._clearFBO1Command;
Color.clone(defaultValue(color, Color.BLACK), clear.color);
clear.execute(context);
clear = this._clearFBO2Command;
Color.clone(defaultValue(color, Color.BLACK), clear.color);
clear.execute(context);
};
SunPostProcess.prototype.execute = function(context, framebuffer) {
this._downSampleCommand.execute(context, this._downSamplePassState);
this._brightPassCommand.execute(context, this._downSamplePassState);
this._blurXCommand.execute(context, this._downSamplePassState);
this._blurYCommand.execute(context, this._downSamplePassState);
this._fullScreenCommand.framebuffer = framebuffer;
this._blendCommand.framebuffer = framebuffer;
this._fullScreenCommand.execute(context);
this._blendCommand.execute(context, this._upSamplePassState);
};
var viewportBoundingRectangle = new BoundingRectangle();
var downSampleViewportBoundingRectangle = new BoundingRectangle();
var sunPositionECScratch = new Cartesian4();
var sunPositionWCScratch = new Cartesian2();
var sizeScratch = new Cartesian2();
var postProcessMatrix4Scratch= new Matrix4();
SunPostProcess.prototype.update = function(passState) {
var context = passState.context;
var viewport = passState.viewport;
var width = context.drawingBufferWidth;
var height = context.drawingBufferHeight;
var that = this;
if (!defined(this._downSampleCommand)) {
this._clearFBO1Command = new ClearCommand({
color : new Color()
});
this._clearFBO2Command = new ClearCommand({
color : new Color()
});
var uniformMap = {};
this._downSampleCommand = context.createViewportQuadCommand(PassThrough, {
uniformMap : uniformMap,
owner : this
});
uniformMap = {
u_avgLuminance : function() {
// A guess at the average luminance across the entire scene
return 0.5;
},
u_threshold : function() {
return 0.25;
},
u_offset : function() {
return 0.1;
}
};
this._brightPassCommand = context.createViewportQuadCommand(BrightPass, {
uniformMap : uniformMap,
owner : this
});
var delta = 1.0;
var sigma = 2.0;
uniformMap = {
delta : function() {
return delta;
},
sigma : function() {
return sigma;
},
direction : function() {
return 0.0;
}
};
this._blurXCommand = context.createViewportQuadCommand(GaussianBlur1D, {
uniformMap : uniformMap,
owner : this
});
uniformMap = {
delta : function() {
return delta;
},
sigma : function() {
return sigma;
},
direction : function() {
return 1.0;
}
};
this._blurYCommand = context.createViewportQuadCommand(GaussianBlur1D, {
uniformMap : uniformMap,
owner : this
});
uniformMap = {
u_center : function() {
return that._uCenter;
},
u_radius : function() {
return that._uRadius;
}
};
this._blendCommand = context.createViewportQuadCommand(AdditiveBlend, {
uniformMap : uniformMap,
owner : this
});
uniformMap = {};
this._fullScreenCommand = context.createViewportQuadCommand(PassThrough, {
uniformMap : uniformMap,
owner : this
});
}
var downSampleWidth = Math.pow(2.0, Math.ceil(Math.log(width) / Math.log(2)) - 2.0);
var downSampleHeight = Math.pow(2.0, Math.ceil(Math.log(height) / Math.log(2)) - 2.0);
// The size computed above can be less than 1.0 if size < 4.0. This will probably
// never happen in practice, but does in the tests. Clamp to 1.0 to prevent WebGL
// errors in the tests.
var downSampleSize = Math.max(1.0, downSampleWidth, downSampleHeight);
var downSampleViewport = downSampleViewportBoundingRectangle;
downSampleViewport.width = downSampleSize;
downSampleViewport.height = downSampleSize;
var fbo = this._fbo;
var colorTexture = (defined(fbo) && fbo.getColorTexture(0)) || undefined;
if (!defined(colorTexture) || colorTexture.width !== width || colorTexture.height !== height) {
fbo = fbo && fbo.destroy();
this._downSampleFBO1 = this._downSampleFBO1 && this._downSampleFBO1.destroy();
this._downSampleFBO2 = this._downSampleFBO2 && this._downSampleFBO2.destroy();
this._blurStep.x = this._blurStep.y = 1.0 / downSampleSize;
var colorTextures = [new Texture({
context : context,
width : width,
height : height
})];
if (context.depthTexture) {
fbo = this._fbo = new Framebuffer({
context : context,
colorTextures :colorTextures,
depthTexture : new Texture({
context : context,
width : width,
height : height,
pixelFormat : PixelFormat.DEPTH_COMPONENT,
pixelDatatype : PixelDatatype.UNSIGNED_SHORT
})
});
} else {
fbo = this._fbo = new Framebuffer({
context : context,
colorTextures : colorTextures,
depthRenderbuffer : new Renderbuffer({
context : context,
format : RenderbufferFormat.DEPTH_COMPONENT16
})
});
}
this._downSampleFBO1 = new Framebuffer({
context : context,
colorTextures : [new Texture({
context : context,
width : downSampleSize,
height : downSampleSize
})]
});
this._downSampleFBO2 = new Framebuffer({
context : context,
colorTextures : [new Texture({
context : context,
width : downSampleSize,
height : downSampleSize
})]
});
this._clearFBO1Command.framebuffer = this._downSampleFBO1;
this._clearFBO2Command.framebuffer = this._downSampleFBO2;
this._downSampleCommand.framebuffer = this._downSampleFBO1;
this._brightPassCommand.framebuffer = this._downSampleFBO2;
this._blurXCommand.framebuffer = this._downSampleFBO1;
this._blurYCommand.framebuffer = this._downSampleFBO2;
var downSampleRenderState = RenderState.fromCache({
viewport : downSampleViewport
});
this._downSampleCommand.uniformMap.u_texture = function() {
return fbo.getColorTexture(0);
};
this._downSampleCommand.renderState = downSampleRenderState;
this._brightPassCommand.uniformMap.u_texture = function() {
return that._downSampleFBO1.getColorTexture(0);
};
this._brightPassCommand.renderState = downSampleRenderState;
this._blurXCommand.uniformMap.u_texture = function() {
return that._downSampleFBO2.getColorTexture(0);
};
this._blurXCommand.uniformMap.u_step = function() {
return that._blurStep;
};
this._blurXCommand.renderState = downSampleRenderState;
this._blurYCommand.uniformMap.u_texture = function() {
return that._downSampleFBO1.getColorTexture(0);
};
this._blurYCommand.uniformMap.u_step = function() {
return that._blurStep;
};
this._blurYCommand.renderState = downSampleRenderState;
var upSampledViewport = viewportBoundingRectangle;
upSampledViewport.width = width;
upSampledViewport.height = height;
var upSampleRenderState = RenderState.fromCache({ viewport : upSampledViewport });
this._blendCommand.uniformMap.u_texture0 = function() {
return fbo.getColorTexture(0);
};
this._blendCommand.uniformMap.u_texture1 = function() {
return that._downSampleFBO2.getColorTexture(0);
};
this._blendCommand.renderState = upSampleRenderState;
this._fullScreenCommand.uniformMap.u_texture = function() {
return fbo.getColorTexture(0);
};
this._fullScreenCommand.renderState = upSampleRenderState;
}
var us = context.uniformState;
var sunPosition = us.sunPositionWC;
var viewMatrix = us.view;
var viewProjectionMatrix = us.viewProjection;
var projectionMatrix = us.projection;
// create up sampled render state
var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, postProcessMatrix4Scratch);
var sunPositionEC = Matrix4.multiplyByPoint(viewMatrix, sunPosition, sunPositionECScratch);
var sunPositionWC = Transforms.pointToGLWindowCoordinates(viewProjectionMatrix, viewportTransformation, sunPosition, sunPositionWCScratch);
sunPositionEC.x += CesiumMath.SOLAR_RADIUS;
var limbWC = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, sunPositionEC, sunPositionEC);
var sunSize = Cartesian2.magnitude(Cartesian2.subtract(limbWC, sunPositionWC, limbWC)) * 30.0 * 2.0;
var size = sizeScratch;
size.x = sunSize;
size.y = sunSize;
var scissorRectangle = this._upSamplePassState.scissorTest.rectangle;
scissorRectangle.x = Math.max(sunPositionWC.x - size.x * 0.5, 0.0);
scissorRectangle.y = Math.max(sunPositionWC.y - size.y * 0.5, 0.0);
scissorRectangle.width = Math.min(size.x, width);
scissorRectangle.height = Math.min(size.y, height);
this._uCenter = Cartesian2.clone(sunPositionWC, this._uCenter);
this._uRadius = Math.max(size.x, size.y) * 0.5;
// create down sampled render state
viewportTransformation = Matrix4.computeViewportTransformation(downSampleViewport, 0.0, 1.0, postProcessMatrix4Scratch);
sunPositionWC = Transforms.pointToGLWindowCoordinates(viewProjectionMatrix, viewportTransformation, sunPosition, sunPositionWCScratch);
size.x *= downSampleWidth / width;
size.y *= downSampleHeight / height;
scissorRectangle = this._downSamplePassState.scissorTest.rectangle;
scissorRectangle.x = Math.max(sunPositionWC.x - size.x * 0.5, 0.0);
scissorRectangle.y = Math.max(sunPositionWC.y - size.y * 0.5, 0.0);
scissorRectangle.width = Math.min(size.x, width);
scissorRectangle.height = Math.min(size.y, height);
this._downSamplePassState.context = context;
this._upSamplePassState.context = context;
return this._fbo;
};
SunPostProcess.prototype.isDestroyed = function() {
return false;
};
SunPostProcess.prototype.destroy = function() {
this._fbo = this._fbo && this._fbo.destroy();
this._downSampleFBO1 = this._downSampleFBO1 && this._downSampleFBO1.destroy();
this._downSampleFBO2 = this._downSampleFBO2 && this._downSampleFBO2.destroy();
this._downSampleCommand = this._downSampleCommand && this._downSampleCommand.shaderProgram && this._downSampleCommand.shaderProgram.destroy();
this._brightPassCommand = this._brightPassCommand && this._brightPassCommand.shaderProgram && this._brightPassCommand.shaderProgram.destroy();
this._blurXCommand = this._blurXCommand && this._blurXCommand.shaderProgram && this._blurXCommand.shaderProgram.destroy();
this._blurYCommand = this._blurYCommand && this._blurYCommand.shaderProgram && this._blurYCommand.shaderProgram.destroy();
this._blendCommand = this._blendCommand && this._blendCommand.shaderProgram && this._blendCommand.shaderProgram.destroy();
this._fullScreenCommand = this._fullScreenCommand && this._fullScreenCommand.shaderProgram && this._fullScreenCommand.shaderProgram.destroy();
return destroyObject(this);
};
return SunPostProcess;
});
/*global define*/
define('Scene/Scene',[
'../Core/BoundingRectangle',
'../Core/BoundingSphere',
'../Core/BoxGeometry',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/Cartographic',
'../Core/Color',
'../Core/ColorGeometryInstanceAttribute',
'../Core/createGuid',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/EllipsoidGeometry',
'../Core/Event',
'../Core/GeographicProjection',
'../Core/GeometryInstance',
'../Core/GeometryPipeline',
'../Core/getTimestamp',
'../Core/Intersect',
'../Core/Interval',
'../Core/JulianDate',
'../Core/Math',
'../Core/Matrix4',
'../Core/mergeSort',
'../Core/Occluder',
'../Core/ShowGeometryInstanceAttribute',
'../Core/Transforms',
'../Renderer/ClearCommand',
'../Renderer/ComputeEngine',
'../Renderer/Context',
'../Renderer/ContextLimits',
'../Renderer/DrawCommand',
'../Renderer/Pass',
'../Renderer/PassState',
'../Renderer/ShaderProgram',
'../Renderer/ShaderSource',
'./Camera',
'./CreditDisplay',
'./CullingVolume',
'./DepthPlane',
'./DeviceOrientationCameraController',
'./Fog',
'./FrameState',
'./FrustumCommands',
'./FXAA',
'./GlobeDepth',
'./MapMode2D',
'./OIT',
'./OrthographicFrustum',
'./PerformanceDisplay',
'./PerInstanceColorAppearance',
'./PerspectiveFrustum',
'./PerspectiveOffCenterFrustum',
'./PickDepth',
'./Primitive',
'./PrimitiveCollection',
'./SceneMode',
'./SceneTransforms',
'./SceneTransitioner',
'./ScreenSpaceCameraController',
'./ShadowMap',
'./SunPostProcess',
'./TweenCollection'
], function(
BoundingRectangle,
BoundingSphere,
BoxGeometry,
Cartesian2,
Cartesian3,
Cartesian4,
Cartographic,
Color,
ColorGeometryInstanceAttribute,
createGuid,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EllipsoidGeometry,
Event,
GeographicProjection,
GeometryInstance,
GeometryPipeline,
getTimestamp,
Intersect,
Interval,
JulianDate,
CesiumMath,
Matrix4,
mergeSort,
Occluder,
ShowGeometryInstanceAttribute,
Transforms,
ClearCommand,
ComputeEngine,
Context,
ContextLimits,
DrawCommand,
Pass,
PassState,
ShaderProgram,
ShaderSource,
Camera,
CreditDisplay,
CullingVolume,
DepthPlane,
DeviceOrientationCameraController,
Fog,
FrameState,
FrustumCommands,
FXAA,
GlobeDepth,
MapMode2D,
OIT,
OrthographicFrustum,
PerformanceDisplay,
PerInstanceColorAppearance,
PerspectiveFrustum,
PerspectiveOffCenterFrustum,
PickDepth,
Primitive,
PrimitiveCollection,
SceneMode,
SceneTransforms,
SceneTransitioner,
ScreenSpaceCameraController,
ShadowMap,
SunPostProcess,
TweenCollection) {
'use strict';
/**
* The container for all 3D graphical objects and state in a Cesium virtual scene. Generally,
* a scene is not created directly; instead, it is implicitly created by {@link CesiumWidget}.
*
* contextOptions
parameter details:
*
*
* The default values are:
*
* {
* webgl : {
* alpha : false,
* depth : true,
* stencil : false,
* antialias : true,
* premultipliedAlpha : true,
* preserveDrawingBuffer : false,
* failIfMajorPerformanceCaveat : false
* },
* allowTextureFilterAnisotropic : true
* }
*
*
*
* The webgl
property corresponds to the {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
* object used to create the WebGL context.
*
*
* webgl.alpha
defaults to false, which can improve performance compared to the standard WebGL default
* of true. If an application needs to composite Cesium above other HTML elements using alpha-blending, set
* webgl.alpha
to true.
*
*
* The other webgl
properties match the WebGL defaults for {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}.
*
*
* allowTextureFilterAnisotropic
defaults to true, which enables anisotropic texture filtering when the
* WebGL extension is supported. Setting this to false will improve performance, but hurt visual quality, especially for horizon views.
*
*
* @alias Scene
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {Canvas} options.canvas The HTML canvas element to create the scene for.
* @param {Object} [options.contextOptions] Context and WebGL creation properties. See details above.
* @param {Element} [options.creditContainer] The HTML element in which the credits will be displayed.
* @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
* @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
* @param {Boolean} [options.scene3DOnly=false] If true, optimizes memory use and performance for 3D mode but disables the ability to use 2D or Columbus View.
* @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
* @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
* @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
*
* @see CesiumWidget
* @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
*
* @exception {DeveloperError} options and options.canvas are required.
*
* @example
* // Create scene without anisotropic texture filtering
* var scene = new Cesium.Scene({
* canvas : canvas,
* contextOptions : {
* allowTextureFilterAnisotropic : false
* }
* });
*/
function Scene(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var canvas = options.canvas;
var contextOptions = options.contextOptions;
var creditContainer = options.creditContainer;
if (!defined(canvas)) {
throw new DeveloperError('options and options.canvas are required.');
}
var context = new Context(canvas, contextOptions);
if (!defined(creditContainer)) {
creditContainer = document.createElement('div');
creditContainer.style.position = 'absolute';
creditContainer.style.bottom = '0';
creditContainer.style['text-shadow'] = '0 0 2px #000000';
creditContainer.style.color = '#ffffff';
creditContainer.style['font-size'] = '10px';
creditContainer.style['padding-right'] = '5px';
canvas.parentNode.appendChild(creditContainer);
}
this._id = createGuid();
this._frameState = new FrameState(context, new CreditDisplay(creditContainer));
this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false);
var ps = new PassState(context);
ps.viewport = new BoundingRectangle();
ps.viewport.x = 0;
ps.viewport.y = 0;
ps.viewport.width = context.drawingBufferWidth;
ps.viewport.height = context.drawingBufferHeight;
this._passState = ps;
this._canvas = canvas;
this._context = context;
this._computeEngine = new ComputeEngine(context);
this._globe = undefined;
this._primitives = new PrimitiveCollection();
this._groundPrimitives = new PrimitiveCollection();
this._tweens = new TweenCollection();
this._shaderFrameCount = 0;
this._sunPostProcess = undefined;
this._computeCommandList = [];
this._frustumCommandsList = [];
this._overlayCommandList = [];
this._pickFramebuffer = undefined;
this._useOIT = defaultValue(options.orderIndependentTranslucency, true);
this._executeOITFunction = undefined;
var globeDepth;
if (context.depthTexture) {
globeDepth = new GlobeDepth();
}
var oit;
if (this._useOIT && defined(globeDepth)) {
oit = new OIT(context);
}
this._globeDepth = globeDepth;
this._depthPlane = new DepthPlane();
this._oit = oit;
this._fxaa = new FXAA();
this._clearColorCommand = new ClearCommand({
color : new Color(),
stencil : 0,
owner : this
});
this._depthClearCommand = new ClearCommand({
depth : 1.0,
owner : this
});
this._stencilClearCommand = new ClearCommand({
stencil : 0
});
this._pickDepths = [];
this._debugGlobeDepths = [];
this._transitioner = new SceneTransitioner(this);
this._renderError = new Event();
this._preRender = new Event();
this._postRender = new Event();
this._cameraStartFired = false;
this._cameraMovedTime = undefined;
/**
* Exceptions occurring in render
are always caught in order to raise the
* renderError
event. If this property is true, the error is rethrown
* after the event is raised. If this property is false, the render
function
* returns normally after raising the event.
*
* @type {Boolean}
* @default false
*/
this.rethrowRenderErrors = false;
/**
* Determines whether or not to instantly complete the
* scene transition animation on user input.
*
* @type {Boolean}
* @default true
*/
this.completeMorphOnUserInput = true;
/**
* The event fired at the beginning of a scene transition.
* @type {Event}
* @default Event()
*/
this.morphStart = new Event();
/**
* The event fired at the completion of a scene transition.
* @type {Event}
* @default Event()
*/
this.morphComplete = new Event();
/**
* The {@link SkyBox} used to draw the stars.
*
* @type {SkyBox}
* @default undefined
*
* @see Scene#backgroundColor
*/
this.skyBox = undefined;
/**
* The sky atmosphere drawn around the globe.
*
* @type {SkyAtmosphere}
* @default undefined
*/
this.skyAtmosphere = undefined;
/**
* The {@link Sun}.
*
* @type {Sun}
* @default undefined
*/
this.sun = undefined;
/**
* Uses a bloom filter on the sun when enabled.
*
* @type {Boolean}
* @default true
*/
this.sunBloom = true;
this._sunBloom = undefined;
/**
* The {@link Moon}
*
* @type Moon
* @default undefined
*/
this.moon = undefined;
/**
* The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is undefined.
*
* @type {Color}
* @default {@link Color.BLACK}
*
* @see Scene#skyBox
*/
this.backgroundColor = Color.clone(Color.BLACK);
this._mode = SceneMode.SCENE3D;
this._mapProjection = defined(options.mapProjection) ? options.mapProjection : new GeographicProjection();
/**
* The current morph transition time between 2D/Columbus View and 3D,
* with 0.0 being 2D or Columbus View and 1.0 being 3D.
*
* @type {Number}
* @default 1.0
*/
this.morphTime = 1.0;
/**
* The far-to-near ratio of the multi-frustum. The default is 1,000.0.
*
* @type {Number}
* @default 1000.0
*/
this.farToNearRatio = 1000.0;
/**
* Determines the uniform depth size in meters of each frustum of the multifrustum in 2D. If a primitive or model close
* to the surface shows z-fighting, decreasing this will eliminate the artifact, but decrease performance. On the
* other hand, increasing this will increase performance but may cause z-fighting among primitives close to thesurface.
* @type {Number}
* @default 1.75e6
*/
this.nearToFarDistance2D = 1.75e6;
/**
* This property is for debugging only; it is not for production use.
*
* A function that determines what commands are executed. As shown in the examples below,
* the function receives the command's owner
as an argument, and returns a boolean indicating if the
* command should be executed.
*
*
* The default is undefined
, indicating that all commands are executed.
*
*
* @type Function
*
* @default undefined
*
* @example
* // Do not execute any commands.
* scene.debugCommandFilter = function(command) {
* return false;
* };
*
* // Execute only the billboard's commands. That is, only draw the billboard.
* var billboards = new Cesium.BillboardCollection();
* scene.debugCommandFilter = function(command) {
* return command.owner === billboards;
* };
*/
this.debugCommandFilter = undefined;
/**
* This property is for debugging only; it is not for production use.
*
* When true
, commands are randomly shaded. This is useful
* for performance analysis to see what parts of a scene or model are
* command-dense and could benefit from batching.
*
*
* @type Boolean
*
* @default false
*/
this.debugShowCommands = false;
/**
* This property is for debugging only; it is not for production use.
*
* When true
, commands are shaded based on the frustums they
* overlap. Commands in the closest frustum are tinted red, commands in
* the next closest are green, and commands in the farthest frustum are
* blue. If a command overlaps more than one frustum, the color components
* are combined, e.g., a command overlapping the first two frustums is tinted
* yellow.
*
*
* @type Boolean
*
* @default false
*/
this.debugShowFrustums = false;
this._debugFrustumStatistics = undefined;
/**
* This property is for debugging only; it is not for production use.
*
* Displays frames per second and time between frames.
*
*
* @type Boolean
*
* @default false
*/
this.debugShowFramesPerSecond = false;
/**
* This property is for debugging only; it is not for production use.
*
* Displays depth information for the indicated frustum.
*
*
* @type Boolean
*
* @default false
*/
this.debugShowGlobeDepth = false;
/**
* This property is for debugging only; it is not for production use.
*
* Indicates which frustum will have depth information displayed.
*
*
* @type Number
*
* @default 1
*/
this.debugShowDepthFrustum = 1;
/**
* When true
, enables Fast Approximate Anti-aliasing even when order independent translucency
* is unsupported.
*
* @type Boolean
* @default true
*/
this.fxaa = true;
/**
* When true
, enables picking using the depth buffer.
*
* @type Boolean
* @default true
*/
this.useDepthPicking = true;
/**
* The time in milliseconds to wait before checking if the camera has not moved and fire the cameraMoveEnd event.
* @type {Number}
* @default 500.0
* @private
*/
this.cameraEventWaitTime = 500.0;
/**
* Set to true to copy the depth texture after rendering the globe. Makes czm_globeDepthTexture valid.
* @type {Boolean}
* @default false
* @private
*/
this.copyGlobeDepth = false;
/**
* Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional
* performance improvements by rendering less geometry and dispatching less terrain requests.
* @type {Fog}
*/
this.fog = new Fog();
this._sunCamera = new Camera(this);
/**
* The shadow map in the scene. When enabled, models, primitives, and the globe may cast and receive shadows.
* By default the light source of the shadow map is the sun.
* @type {ShadowMap}
*/
this.shadowMap = new ShadowMap({
context : context,
lightCamera : this._sunCamera,
enabled : defaultValue(options.shadows, false)
});
this._terrainExaggeration = defaultValue(options.terrainExaggeration, 1.0);
this._performanceDisplay = undefined;
this._debugVolume = undefined;
var camera = new Camera(this);
this._camera = camera;
this._cameraClone = Camera.clone(camera);
this._screenSpaceCameraController = new ScreenSpaceCameraController(this);
this._mapMode2D = defaultValue(options.mapMode2D, MapMode2D.INFINITE_SCROLL);
// Keeps track of the state of a frame. FrameState is the state across
// the primitives of the scene. This state is for internally keeping track
// of celestial and environment effects that need to be updated/rendered in
// a certain order as well as updating/tracking framebuffer usage.
this._environmentState = {
skyBoxCommand : undefined,
skyAtmosphereCommand : undefined,
sunDrawCommand : undefined,
sunComputeCommand : undefined,
moonCommand : undefined,
isSunVisible : false,
isMoonVisible : false,
isReadyForAtmosphere : false,
isSkyAtmosphereVisible : false,
clearGlobeDepth : false,
useDepthPlane : false,
originalFramebuffer : undefined,
useGlobeDepthFramebuffer : false,
useOIT : false,
useFXAA : false
};
this._useWebVR = false;
this._cameraVR = undefined;
this._aspectRatioVR = undefined;
// initial guess at frustums.
var near = camera.frustum.near;
var far = camera.frustum.far;
var numFrustums = Math.ceil(Math.log(far / near) / Math.log(this.farToNearRatio));
updateFrustums(near, far, this.farToNearRatio, numFrustums, this._frustumCommandsList, false, undefined);
// give frameState, camera, and screen space camera controller initial state before rendering
updateFrameState(this, 0.0, JulianDate.now());
this.initializeFrame();
}
var OPAQUE_FRUSTUM_NEAR_OFFSET = 0.99;
defineProperties(Scene.prototype, {
/**
* Gets the canvas element to which this scene is bound.
* @memberof Scene.prototype
*
* @type {Canvas}
* @readonly
*/
canvas : {
get : function() {
return this._canvas;
}
},
/**
* The drawingBufferWidth of the underlying GL context.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferWidth|drawingBufferWidth}
*/
drawingBufferHeight : {
get : function() {
return this._context.drawingBufferHeight;
}
},
/**
* The drawingBufferHeight of the underlying GL context.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
*/
drawingBufferWidth : {
get : function() {
return this._context.drawingBufferWidth;
}
},
/**
* The maximum aliased line width, in pixels, supported by this WebGL implementation. It will be at least one.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with ALIASED_LINE_WIDTH_RANGE
.
*/
maximumAliasedLineWidth : {
get : function() {
return ContextLimits.maximumAliasedLineWidth;
}
},
/**
* The maximum length in pixels of one edge of a cube map, supported by this WebGL implementation. It will be at least 16.
* @memberof Scene.prototype
*
* @type {Number}
* @readonly
*
* @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with GL_MAX_CUBE_MAP_TEXTURE_SIZE
.
*/
maximumCubeMapSize : {
get : function() {
return ContextLimits.maximumCubeMapSize;
}
},
/**
* Returns true if the pickPosition function is supported.
* @memberof Scene.prototype
*
* @type {Boolean}
* @readonly
*/
pickPositionSupported : {
get : function() {
return this._context.depthTexture;
}
},
/**
* Gets or sets the depth-test ellipsoid.
* @memberof Scene.prototype
*
* @type {Globe}
*/
globe : {
get: function() {
return this._globe;
},
set: function(globe) {
this._globe = this._globe && this._globe.destroy();
this._globe = globe;
}
},
/**
* Gets the collection of primitives.
* @memberof Scene.prototype
*
* @type {PrimitiveCollection}
* @readonly
*/
primitives : {
get : function() {
return this._primitives;
}
},
/**
* Gets the collection of ground primitives.
* @memberof Scene.prototype
*
* @type {PrimitiveCollection}
* @readonly
*/
groundPrimitives : {
get : function() {
return this._groundPrimitives;
}
},
/**
* Gets the camera.
* @memberof Scene.prototype
*
* @type {Camera}
* @readonly
*/
camera : {
get : function() {
return this._camera;
}
},
// TODO: setCamera
/**
* Gets the controller for camera input handling.
* @memberof Scene.prototype
*
* @type {ScreenSpaceCameraController}
* @readonly
*/
screenSpaceCameraController : {
get : function() {
return this._screenSpaceCameraController;
}
},
/**
* Get the map projection to use in 2D and Columbus View modes.
* @memberof Scene.prototype
*
* @type {MapProjection}
* @readonly
*
* @default new GeographicProjection()
*/
mapProjection : {
get: function() {
return this._mapProjection;
}
},
/**
* Gets state information about the current scene. If called outside of a primitive's update
* function, the previous frame's state is returned.
* @memberof Scene.prototype
*
* @type {FrameState}
* @readonly
*
* @private
*/
frameState : {
get: function() {
return this._frameState;
}
},
/**
* Gets the collection of tweens taking place in the scene.
* @memberof Scene.prototype
*
* @type {TweenCollection}
* @readonly
*
* @private
*/
tweens : {
get : function() {
return this._tweens;
}
},
/**
* Gets the collection of image layers that will be rendered on the globe.
* @memberof Scene.prototype
*
* @type {ImageryLayerCollection}
* @readonly
*/
imageryLayers : {
get : function() {
return this.globe.imageryLayers;
}
},
/**
* The terrain provider providing surface geometry for the globe.
* @memberof Scene.prototype
*
* @type {TerrainProvider}
*/
terrainProvider : {
get : function() {
return this.globe.terrainProvider;
},
set : function(terrainProvider) {
this.globe.terrainProvider = terrainProvider;
}
},
/**
* Gets an event that's raised when the terrain provider is changed
* @memberof Scene.prototype
*
* @type {Event}
* @readonly
*/
terrainProviderChanged : {
get : function() {
return this.globe.terrainProviderChanged;
}
},
/**
* Gets the event that will be raised when an error is thrown inside the render
function.
* The Scene instance and the thrown error are the only two parameters passed to the event handler.
* By default, errors are not rethrown after this event is raised, but that can be changed by setting
* the rethrowRenderErrors
property.
* @memberof Scene.prototype
*
* @type {Event}
* @readonly
*/
renderError : {
get : function() {
return this._renderError;
}
},
/**
* Gets the event that will be raised at the start of each call to render
. Subscribers to the event
* receive the Scene instance as the first parameter and the current time as the second parameter.
* @memberof Scene.prototype
*
* @type {Event}
* @readonly
*/
preRender : {
get : function() {
return this._preRender;
}
},
/**
* Gets the event that will be raised at the end of each call to render
. Subscribers to the event
* receive the Scene instance as the first parameter and the current time as the second parameter.
* @memberof Scene.prototype
*
* @type {Event}
* @readonly
*/
postRender : {
get : function() {
return this._postRender;
}
},
/**
* @memberof Scene.prototype
* @private
* @readonly
*/
context : {
get : function() {
return this._context;
}
},
/**
* This property is for debugging only; it is not for production use.
*
* When {@link Scene.debugShowFrustums} is true
, this contains
* properties with statistics about the number of command execute per frustum.
* totalCommands
is the total number of commands executed, ignoring
* overlap. commandsInFrustums
is an array with the number of times
* commands are executed redundantly, e.g., how many commands overlap two or
* three frustums.
*
*
* @memberof Scene.prototype
*
* @type {Object}
* @readonly
*
* @default undefined
*/
debugFrustumStatistics : {
get : function() {
return this._debugFrustumStatistics;
}
},
/**
* Gets whether or not the scene is optimized for 3D only viewing.
* @memberof Scene.prototype
* @type {Boolean}
* @readonly
*/
scene3DOnly : {
get : function() {
return this._frameState.scene3DOnly;
}
},
/**
* Gets whether or not the scene has order independent translucency enabled.
* Note that this only reflects the original construction option, and there are
* other factors that could prevent OIT from functioning on a given system configuration.
* @memberof Scene.prototype
* @type {Boolean}
* @readonly
*/
orderIndependentTranslucency : {
get : function() {
return defined(this._oit);
}
},
/**
* Gets the unique identifier for this scene.
* @memberof Scene.prototype
* @type {String}
* @readonly
*/
id : {
get : function() {
return this._id;
}
},
/**
* Gets or sets the current mode of the scene.
* @memberof Scene.prototype
* @type {SceneMode}
* @default {@link SceneMode.SCENE3D}
*/
mode : {
get : function() {
return this._mode;
},
set : function(value) {
if (this.scene3DOnly && value !== SceneMode.SCENE3D) {
throw new DeveloperError('Only SceneMode.SCENE3D is valid when scene3DOnly is true.');
}
if (value === SceneMode.SCENE2D) {
this.morphTo2D(0);
} else if (value === SceneMode.SCENE3D) {
this.morphTo3D(0);
} else if (value === SceneMode.COLUMBUS_VIEW) {
this.morphToColumbusView(0);
} else {
throw new DeveloperError('value must be a valid SceneMode enumeration.');
}
this._mode = value;
}
},
/**
* Gets the number of frustums used in the last frame.
* @memberof Scene.prototype
* @type {Number}
*
* @private
*/
numberOfFrustums : {
get : function() {
return this._frustumCommandsList.length;
}
},
/**
* Gets the scalar used to exaggerate the terrain.
* @memberof Scene.prototype
* @type {Number}
*/
terrainExaggeration : {
get : function() {
return this._terrainExaggeration;
}
},
/**
* When true
, splits the scene into two viewports with steroscopic views for the left and right eyes.
* Used for cardboard and WebVR.
* @memberof Scene.prototype
* @type {Boolean}
* @default false
*/
useWebVR : {
get : function() {
return this._useWebVR;
},
set : function(value) {
this._useWebVR = value;
if (this._useWebVR) {
this._frameState.creditDisplay.container.style.visibility = 'hidden';
this._cameraVR = new Camera(this);
if (!defined(this._deviceOrientationCameraController)) {
this._deviceOrientationCameraController = new DeviceOrientationCameraController(this);
}
this._aspectRatioVR = this._camera.frustum.aspectRatio;
} else {
this._frameState.creditDisplay.container.style.visibility = 'visible';
this._cameraVR = undefined;
this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy();
this._camera.frustum.aspectRatio = this._aspectRatioVR;
this._camera.frustum.xOffset = 0.0;
}
}
},
/**
* Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
* @memberof Scene.prototype
* @type {Boolean}
*/
mapMode2D : {
get : function() {
return this._mapMode2D;
}
}
});
var scratchPosition0 = new Cartesian3();
var scratchPosition1 = new Cartesian3();
function maxComponent(a, b) {
var x = Math.max(Math.abs(a.x), Math.abs(b.x));
var y = Math.max(Math.abs(a.y), Math.abs(b.y));
var z = Math.max(Math.abs(a.z), Math.abs(b.z));
return Math.max(Math.max(x, y), z);
}
function cameraEqual(camera0, camera1, epsilon) {
var scalar = 1 / Math.max(1, maxComponent(camera0.position, camera1.position));
Cartesian3.multiplyByScalar(camera0.position, scalar, scratchPosition0);
Cartesian3.multiplyByScalar(camera1.position, scalar, scratchPosition1);
return Cartesian3.equalsEpsilon(scratchPosition0, scratchPosition1, epsilon) &&
Cartesian3.equalsEpsilon(camera0.direction, camera1.direction, epsilon) &&
Cartesian3.equalsEpsilon(camera0.up, camera1.up, epsilon) &&
Cartesian3.equalsEpsilon(camera0.right, camera1.right, epsilon) &&
Matrix4.equalsEpsilon(camera0.transform, camera1.transform, epsilon);
}
function updateDerivedCommands(scene, command) {
var frameState = scene.frameState;
var context = scene._context;
var shadowsEnabled = frameState.shadowHints.shadowsEnabled;
var shadowMaps = frameState.shadowHints.shadowMaps;
var lightShadowMaps = frameState.shadowHints.lightShadowMaps;
var lightShadowsEnabled = shadowsEnabled && (lightShadowMaps.length > 0);
// Update derived commands when any shadow maps become dirty
var shadowsDirty = false;
var lastDirtyTime = frameState.shadowHints.lastDirtyTime;
if (command.lastDirtyTime !== lastDirtyTime) {
command.lastDirtyTime = lastDirtyTime;
command.dirty = true;
shadowsDirty = true;
}
if (command.dirty) {
command.dirty = false;
var derivedCommands = command.derivedCommands;
if (shadowsEnabled && (command.receiveShadows || command.castShadows)) {
derivedCommands.shadows = ShadowMap.createDerivedCommands(shadowMaps, lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows);
}
var oit = scene._oit;
if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) {
if (lightShadowsEnabled && command.receiveShadows) {
derivedCommands.oit = defined(derivedCommands.oit) ? derivedCommands.oit : {};
derivedCommands.oit.shadows = oit.createDerivedCommands(command.derivedCommands.shadows.receiveCommand, context, derivedCommands.oit.shadows);
} else {
derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit);
}
}
}
}
var scratchOccluderBoundingSphere = new BoundingSphere();
var scratchOccluder;
function getOccluder(scene) {
// TODO: The occluder is the top-level globe. When we add
// support for multiple central bodies, this should be the closest one.
var globe = scene.globe;
if (scene._mode === SceneMode.SCENE3D && defined(globe)) {
var ellipsoid = globe.ellipsoid;
scratchOccluderBoundingSphere.radius = ellipsoid.minimumRadius;
scratchOccluder = Occluder.fromBoundingSphere(scratchOccluderBoundingSphere, scene._camera.positionWC, scratchOccluder);
return scratchOccluder;
}
return undefined;
}
function clearPasses(passes) {
passes.render = false;
passes.pick = false;
}
function updateFrameState(scene, frameNumber, time) {
var camera = scene._camera;
var frameState = scene._frameState;
frameState.commandList.length = 0;
frameState.shadowMaps.length = 0;
frameState.mode = scene._mode;
frameState.morphTime = scene.morphTime;
frameState.mapProjection = scene.mapProjection;
frameState.frameNumber = frameNumber;
frameState.time = JulianDate.clone(time, frameState.time);
frameState.camera = camera;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
frameState.occluder = getOccluder(scene);
frameState.terrainExaggeration = scene._terrainExaggeration;
clearPasses(frameState.passes);
}
function updateFrustums(near, far, farToNearRatio, numFrustums, frustumCommandsList, is2D, nearToFarDistance2D) {
frustumCommandsList.length = numFrustums;
for (var m = 0; m < numFrustums; ++m) {
var curNear;
var curFar;
if (!is2D) {
curNear = Math.max(near, Math.pow(farToNearRatio, m) * near);
curFar = Math.min(far, farToNearRatio * curNear);
} else {
curNear = Math.min(far - nearToFarDistance2D, near + m * nearToFarDistance2D);
curFar = Math.min(far, curNear + nearToFarDistance2D);
}
var frustumCommands = frustumCommandsList[m];
if (!defined(frustumCommands)) {
frustumCommands = frustumCommandsList[m] = new FrustumCommands(curNear, curFar);
} else {
frustumCommands.near = curNear;
frustumCommands.far = curFar;
}
}
}
function insertIntoBin(scene, command, distance) {
if (scene.debugShowFrustums) {
command.debugOverlappingFrustums = 0;
}
if (!scene.frameState.passes.pick) {
updateDerivedCommands(scene, command);
}
var frustumCommandsList = scene._frustumCommandsList;
var length = frustumCommandsList.length;
for (var i = 0; i < length; ++i) {
var frustumCommands = frustumCommandsList[i];
var curNear = frustumCommands.near;
var curFar = frustumCommands.far;
if (distance.start > curFar) {
continue;
}
if (distance.stop < curNear) {
break;
}
var pass = command instanceof ClearCommand ? Pass.OPAQUE : command.pass;
var index = frustumCommands.indices[pass]++;
frustumCommands.commands[pass][index] = command;
if (scene.debugShowFrustums) {
command.debugOverlappingFrustums |= (1 << i);
}
if (command.executeInClosestFrustum) {
break;
}
}
if (scene.debugShowFrustums) {
var cf = scene._debugFrustumStatistics.commandsInFrustums;
cf[command.debugOverlappingFrustums] = defined(cf[command.debugOverlappingFrustums]) ? cf[command.debugOverlappingFrustums] + 1 : 1;
++scene._debugFrustumStatistics.totalCommands;
}
}
var scratchCullingVolume = new CullingVolume();
var distances = new Interval();
function isVisible(command, cullingVolume, occluder) {
return ((defined(command)) &&
((!defined(command.boundingVolume)) ||
!command.cull ||
((cullingVolume.computeVisibility(command.boundingVolume) !== Intersect.OUTSIDE) &&
(!defined(occluder) || !command.boundingVolume.isOccluded(occluder)))));
}
function createPotentiallyVisibleSet(scene) {
var frameState = scene._frameState;
var camera = frameState.camera;
var direction = camera.directionWC;
var position = camera.positionWC;
var computeList = scene._computeCommandList;
var overlayList = scene._overlayCommandList;
var commandList = frameState.commandList;
if (scene.debugShowFrustums) {
scene._debugFrustumStatistics = {
totalCommands : 0,
commandsInFrustums : {}
};
}
var frustumCommandsList = scene._frustumCommandsList;
var numberOfFrustums = frustumCommandsList.length;
var numberOfPasses = Pass.NUMBER_OF_PASSES;
for (var n = 0; n < numberOfFrustums; ++n) {
for (var p = 0; p < numberOfPasses; ++p) {
frustumCommandsList[n].indices[p] = 0;
}
}
computeList.length = 0;
overlayList.length = 0;
var near = Number.MAX_VALUE;
var far = -Number.MAX_VALUE;
var undefBV = false;
var shadowsEnabled = frameState.shadowHints.shadowsEnabled;
var shadowNear = Number.MAX_VALUE;
var shadowFar = -Number.MAX_VALUE;
var shadowClosestObjectSize = Number.MAX_VALUE;
var occluder = (frameState.mode === SceneMode.SCENE3D) ? frameState.occluder: undefined;
var cullingVolume = frameState.cullingVolume;
// get user culling volume minus the far plane.
var planes = scratchCullingVolume.planes;
for (var k = 0; k < 5; ++k) {
planes[k] = cullingVolume.planes[k];
}
cullingVolume = scratchCullingVolume;
// Determine visibility of celestial and terrestrial environment effects.
var environmentState = scene._environmentState;
environmentState.isSkyAtmosphereVisible = defined(environmentState.skyAtmosphereCommand) && environmentState.isReadyForAtmosphere;
environmentState.isSunVisible = isVisible(environmentState.sunDrawCommand, cullingVolume, occluder);
environmentState.isMoonVisible = isVisible(environmentState.moonCommand, cullingVolume, occluder);
var length = commandList.length;
for (var i = 0; i < length; ++i) {
var command = commandList[i];
var pass = command.pass;
if (pass === Pass.COMPUTE) {
computeList.push(command);
} else if (pass === Pass.OVERLAY) {
overlayList.push(command);
} else {
var boundingVolume = command.boundingVolume;
if (defined(boundingVolume)) {
if (!isVisible(command, cullingVolume, occluder)) {
continue;
}
distances = boundingVolume.computePlaneDistances(position, direction, distances);
near = Math.min(near, distances.start);
far = Math.max(far, distances.stop);
// Compute a tight near and far plane for commands that receive shadows. This helps compute
// good splits for cascaded shadow maps. Ignore commands that exceed the maximum distance.
// When moving the camera low LOD globe tiles begin to load, whose bounding volumes
// throw off the near/far fitting for the shadow map. Only update for globe tiles that the
// camera isn't inside.
if (shadowsEnabled && command.receiveShadows && (distances.start < ShadowMap.MAXIMUM_DISTANCE) &&
!((pass === Pass.GLOBE) && (distances.start < -100.0) && (distances.stop > 100.0))) {
// Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object.
var size = distances.stop - distances.start;
if ((pass !== Pass.GLOBE) && (distances.start < 100.0)) {
shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size);
}
shadowNear = Math.min(shadowNear, distances.start);
shadowFar = Math.max(shadowFar, distances.stop);
}
} else {
// Clear commands don't need a bounding volume - just add the clear to all frustums.
// If another command has no bounding volume, though, we need to use the camera's
// worst-case near and far planes to avoid clipping something important.
distances.start = camera.frustum.near;
distances.stop = camera.frustum.far;
undefBV = !(command instanceof ClearCommand);
}
insertIntoBin(scene, command, distances);
}
}
if (undefBV) {
near = camera.frustum.near;
far = camera.frustum.far;
} else {
// The computed near plane must be between the user defined near and far planes.
// The computed far plane must between the user defined far and computed near.
// This will handle the case where the computed near plane is further than the user defined far plane.
near = Math.min(Math.max(near, camera.frustum.near), camera.frustum.far);
far = Math.max(Math.min(far, camera.frustum.far), near);
if (shadowsEnabled) {
shadowNear = Math.min(Math.max(shadowNear, camera.frustum.near), camera.frustum.far);
shadowFar = Math.max(Math.min(shadowFar, camera.frustum.far), shadowNear);
}
}
// Use the computed near and far for shadows
if (shadowsEnabled) {
frameState.shadowHints.nearPlane = shadowNear;
frameState.shadowHints.farPlane = shadowFar;
frameState.shadowHints.closestObjectSize = shadowClosestObjectSize;
}
// Exploit temporal coherence. If the frustums haven't changed much, use the frustums computed
// last frame, else compute the new frustums and sort them by frustum again.
var is2D = scene.mode === SceneMode.SCENE2D;
var farToNearRatio = scene.farToNearRatio;
var numFrustums;
if (!is2D) {
// The multifrustum for 3D/CV is non-uniformly distributed.
numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
} else {
// The multifrustum for 2D is uniformly distributed. To avoid z-fighting in 2D,
// the camera i smoved to just before the frustum and the frustum depth is scaled
// to be in [1.0, nearToFarDistance2D].
far = Math.min(far, camera.position.z + scene.nearToFarDistance2D);
near = Math.min(near, far);
numFrustums = Math.ceil(Math.max(1.0, far - near) / scene.nearToFarDistance2D);
}
if (near !== Number.MAX_VALUE && (numFrustums !== numberOfFrustums || (frustumCommandsList.length !== 0 &&
(near < frustumCommandsList[0].near || far > frustumCommandsList[numberOfFrustums - 1].far)))) {
updateFrustums(near, far, farToNearRatio, numFrustums, frustumCommandsList, is2D, scene.nearToFarDistance2D);
createPotentiallyVisibleSet(scene);
}
}
function getAttributeLocations(shaderProgram) {
var attributeLocations = {};
var attributes = shaderProgram.vertexAttributes;
for (var a in attributes) {
if (attributes.hasOwnProperty(a)) {
attributeLocations[a] = attributes[a].index;
}
}
return attributeLocations;
}
function createDebugFragmentShaderProgram(command, scene, shaderProgram) {
var context = scene.context;
var sp = defaultValue(shaderProgram, command.shaderProgram);
var fs = sp.fragmentShaderSource.clone();
fs.sources = fs.sources.map(function(source) {
source = ShaderSource.replaceMain(source, 'czm_Debug_main');
return source;
});
var newMain =
'void main() \n' +
'{ \n' +
' czm_Debug_main(); \n';
if (scene.debugShowCommands) {
if (!defined(command._debugColor)) {
command._debugColor = Color.fromRandom();
}
var c = command._debugColor;
newMain += ' gl_FragColor.rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
}
if (scene.debugShowFrustums) {
// Support up to three frustums. If a command overlaps all
// three, it's code is not changed.
var r = (command.debugOverlappingFrustums & (1 << 0)) ? '1.0' : '0.0';
var g = (command.debugOverlappingFrustums & (1 << 1)) ? '1.0' : '0.0';
var b = (command.debugOverlappingFrustums & (1 << 2)) ? '1.0' : '0.0';
newMain += ' gl_FragColor.rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
}
newMain += '}';
fs.sources.push(newMain);
var attributeLocations = getAttributeLocations(sp);
return ShaderProgram.fromCache({
context : context,
vertexShaderSource : sp.vertexShaderSource,
fragmentShaderSource : fs,
attributeLocations : attributeLocations
});
}
function executeDebugCommand(command, scene, passState) {
var debugCommand = DrawCommand.shallowClone(command);
debugCommand.shaderProgram = createDebugFragmentShaderProgram(command, scene);
debugCommand.execute(scene.context, passState);
debugCommand.shaderProgram.destroy();
}
var transformFrom2D = new Matrix4(0.0, 0.0, 1.0, 0.0,
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0);
transformFrom2D = Matrix4.inverseTransformation(transformFrom2D, transformFrom2D);
function executeCommand(command, scene, context, passState, debugFramebuffer) {
if ((defined(scene.debugCommandFilter)) && !scene.debugCommandFilter(command)) {
return;
}
var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled;
var lightShadowsEnabled = shadowsEnabled && (scene.frameState.shadowHints.lightShadowMaps.length > 0);
if (scene.debugShowCommands || scene.debugShowFrustums) {
executeDebugCommand(command, scene, passState);
} else if (lightShadowsEnabled && command.receiveShadows && defined(command.derivedCommands.shadows)) {
// If the command receives shadows, execute the derived shadows command.
// Some commands, such as OIT derived commands, do not have derived shadow commands themselves
// and instead shadowing is built-in. In this case execute the command regularly below.
command.derivedCommands.shadows.receiveCommand.execute(context, passState);
} else {
command.execute(context, passState);
}
if (command.debugShowBoundingVolume && (defined(command.boundingVolume))) {
// Debug code to draw bounding volume for command. Not optimized!
// Assumes bounding volume is a bounding sphere or box
var frameState = scene._frameState;
var boundingVolume = command.boundingVolume;
if (defined(scene._debugVolume)) {
scene._debugVolume.destroy();
}
var geometry;
var center = Cartesian3.clone(boundingVolume.center);
if (frameState.mode !== SceneMode.SCENE3D) {
center = Matrix4.multiplyByPoint(transformFrom2D, center, center);
var projection = frameState.mapProjection;
var centerCartographic = projection.unproject(center);
center = projection.ellipsoid.cartographicToCartesian(centerCartographic);
}
if (defined(boundingVolume.radius)) {
var radius = boundingVolume.radius;
geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
radii : new Cartesian3(radius, radius, radius),
vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
})));
scene._debugVolume = new Primitive({
geometryInstances : new GeometryInstance({
geometry : geometry,
modelMatrix : Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, new Matrix4()),
attributes : {
color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : false
}),
asynchronous : false
});
} else {
var halfAxes = boundingVolume.halfAxes;
geometry = GeometryPipeline.toWireframe(BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
dimensions : new Cartesian3(2.0, 2.0, 2.0),
vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
})));
scene._debugVolume = new Primitive({
geometryInstances : new GeometryInstance({
geometry : geometry,
modelMatrix : Matrix4.fromRotationTranslation(halfAxes, center, new Matrix4()),
attributes : {
color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
}
}),
appearance : new PerInstanceColorAppearance({
flat : true,
translucent : false
}),
asynchronous : false
});
}
var savedCommandList = frameState.commandList;
var commandList = frameState.commandList = [];
scene._debugVolume.update(frameState);
var framebuffer;
if (defined(debugFramebuffer)) {
framebuffer = passState.framebuffer;
passState.framebuffer = debugFramebuffer;
}
commandList[0].execute(context, passState);
if (defined(framebuffer)) {
passState.framebuffer = framebuffer;
}
frameState.commandList = savedCommandList;
}
}
function translucentCompare(a, b, position) {
return b.boundingVolume.distanceSquaredTo(position) - a.boundingVolume.distanceSquaredTo(position);
}
function executeTranslucentCommandsSorted(scene, executeFunction, passState, commands) {
var context = scene.context;
mergeSort(commands, translucentCompare, scene._camera.positionWC);
var length = commands.length;
for (var j = 0; j < length; ++j) {
executeFunction(commands[j], scene, context, passState);
}
}
function getDebugGlobeDepth(scene, index) {
var globeDepth = scene._debugGlobeDepths[index];
if (!defined(globeDepth) && scene.context.depthTexture) {
globeDepth = new GlobeDepth();
scene._debugGlobeDepths[index] = globeDepth;
}
return globeDepth;
}
function getPickDepth(scene, index) {
var pickDepth = scene._pickDepths[index];
if (!defined(pickDepth)) {
pickDepth = new PickDepth();
scene._pickDepths[index] = pickDepth;
}
return pickDepth;
}
var scratchPerspectiveFrustum = new PerspectiveFrustum();
var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
var scratchOrthographicFrustum = new OrthographicFrustum();
function executeCommands(scene, passState) {
var camera = scene._camera;
var context = scene.context;
var us = context.uniformState;
us.updateCamera(camera);
// Create a working frustum from the original camera frustum.
var frustum;
if (defined(camera.frustum.fov)) {
frustum = camera.frustum.clone(scratchPerspectiveFrustum);
} else if (defined(camera.frustum.infiniteProjectionMatrix)){
frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
} else {
frustum = camera.frustum.clone(scratchOrthographicFrustum);
}
// Ideally, we would render the sky box and atmosphere last for
// early-z, but we would have to draw it in each frustum
frustum.near = camera.frustum.near;
frustum.far = camera.frustum.far;
us.updateFrustum(frustum);
us.updatePass(Pass.ENVIRONMENT);
var environmentState = scene._environmentState;
var skyBoxCommand = environmentState.skyBoxCommand;
if (defined(skyBoxCommand)) {
executeCommand(skyBoxCommand, scene, context, passState);
}
if (environmentState.isSkyAtmosphereVisible) {
executeCommand(environmentState.skyAtmosphereCommand, scene, context, passState);
}
var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D;
if (environmentState.isSunVisible) {
environmentState.sunDrawCommand.execute(context, passState);
if (scene.sunBloom && !useWebVR) {
var framebuffer;
if (environmentState.useGlobeDepthFramebuffer) {
framebuffer = scene._globeDepth.framebuffer;
} else if (environmentState.useFXAA) {
framebuffer = scene._fxaa.getColorFramebuffer();
} else {
framebuffer = environmentState.originalFramebuffer;
}
scene._sunPostProcess.execute(context, framebuffer);
passState.framebuffer = framebuffer;
}
}
// Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere.
if (environmentState.isMoonVisible) {
environmentState.moonCommand.execute(context, passState);
}
// Determine how translucent surfaces will be handled.
var executeTranslucentCommands;
if (environmentState.useOIT) {
if (!defined(scene._executeOITFunction)) {
scene._executeOITFunction = function(scene, executeFunction, passState, commands) {
scene._oit.executeCommands(scene, executeFunction, passState, commands);
};
}
executeTranslucentCommands = scene._executeOITFunction;
} else {
executeTranslucentCommands = executeTranslucentCommandsSorted;
}
var clearGlobeDepth = environmentState.clearGlobeDepth;
var useDepthPlane = environmentState.useDepthPlane;
var clearDepth = scene._depthClearCommand;
var depthPlane = scene._depthPlane;
var height2D = camera.position.z;
// Execute commands in each frustum in back to front order
var j;
var frustumCommandsList = scene._frustumCommandsList;
var numFrustums = frustumCommandsList.length;
for (var i = 0; i < numFrustums; ++i) {
var index = numFrustums - i - 1;
var frustumCommands = frustumCommandsList[index];
if (scene.mode === SceneMode.SCENE2D) {
// To avoid z-fighting in 2D, move the camera to just before the frustum
// and scale the frustum depth to be in [1.0, nearToFarDistance2D].
camera.position.z = height2D - frustumCommands.near + 1.0;
frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near);
frustum.near = 1.0;
us.update(scene.frameState);
us.updateFrustum(frustum);
} else {
// Avoid tearing artifacts between adjacent frustums in the opaque passes
frustum.near = index !== 0 ? frustumCommands.near * OPAQUE_FRUSTUM_NEAR_OFFSET : frustumCommands.near;
frustum.far = frustumCommands.far;
us.updateFrustum(frustum);
}
var globeDepth = scene.debugShowGlobeDepth ? getDebugGlobeDepth(scene, index) : scene._globeDepth;
var fb;
if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
fb = passState.framebuffer;
passState.framebuffer = globeDepth.framebuffer;
}
clearDepth.execute(context, passState);
us.updatePass(Pass.GLOBE);
var commands = frustumCommands.commands[Pass.GLOBE];
var length = frustumCommands.indices[Pass.GLOBE];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && (scene.copyGlobeDepth || scene.debugShowGlobeDepth)) {
globeDepth.update(context);
globeDepth.executeCopyDepth(context, passState);
}
if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
passState.framebuffer = fb;
}
us.updatePass(Pass.GROUND);
commands = frustumCommands.commands[Pass.GROUND];
length = frustumCommands.indices[Pass.GROUND];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
// Clear the stencil after the ground pass
if (length > 0 && context.stencilBuffer) {
scene._stencilClearCommand.execute(context, passState);
}
if (clearGlobeDepth) {
clearDepth.execute(context, passState);
if (useDepthPlane) {
depthPlane.execute(context, passState);
}
}
// Execute commands in order by pass up to the translucent pass.
// Translucent geometry needs special handling (sorting/OIT).
var startPass = Pass.GROUND + 1;
var endPass = Pass.TRANSLUCENT;
for (var pass = startPass; pass < endPass; ++pass) {
us.updatePass(pass);
commands = frustumCommands.commands[pass];
length = frustumCommands.indices[pass];
for (j = 0; j < length; ++j) {
executeCommand(commands[j], scene, context, passState);
}
}
if (index !== 0 && scene.mode !== SceneMode.SCENE2D) {
// Do not overlap frustums in the translucent pass to avoid blending artifacts
frustum.near = frustumCommands.near;
us.updateFrustum(frustum);
}
us.updatePass(Pass.TRANSLUCENT);
commands = frustumCommands.commands[Pass.TRANSLUCENT];
commands.length = frustumCommands.indices[Pass.TRANSLUCENT];
executeTranslucentCommands(scene, executeCommand, passState, commands);
if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && scene.useDepthPicking) {
// PERFORMANCE_IDEA: Use MRT to avoid the extra copy.
var pickDepth = getPickDepth(scene, index);
pickDepth.update(context, globeDepth.framebuffer.depthStencilTexture);
pickDepth.executeCopyDepth(context, passState);
}
}
}
function executeComputeCommands(scene) {
var us = scene.context.uniformState;
us.updatePass(Pass.COMPUTE);
var sunComputeCommand = scene._environmentState.sunComputeCommand;
if (defined(sunComputeCommand)) {
sunComputeCommand.execute(scene._computeEngine);
}
var commandList = scene._computeCommandList;
var length = commandList.length;
for (var i = 0; i < length; ++i) {
commandList[i].execute(scene._computeEngine);
}
}
function executeOverlayCommands(scene, passState) {
var us = scene.context.uniformState;
us.updatePass(Pass.OVERLAY);
var context = scene.context;
var commandList = scene._overlayCommandList;
var length = commandList.length;
for (var i = 0; i < length; ++i) {
commandList[i].execute(context, passState);
}
}
function insertShadowCastCommands(scene, commandList, shadowMap) {
var shadowVolume = shadowMap.shadowMapCullingVolume;
var isPointLight = shadowMap.isPointLight;
var passes = shadowMap.passes;
var numberOfPasses = passes.length;
var length = commandList.length;
for (var i = 0; i < length; ++i) {
var command = commandList[i];
updateDerivedCommands(scene, command);
if (command.castShadows && (command.pass === Pass.GLOBE || command.pass === Pass.OPAQUE || command.pass === Pass.TRANSLUCENT)) {
if (isVisible(command, shadowVolume)) {
if (isPointLight) {
for (var k = 0; k < numberOfPasses; ++k) {
passes[k].commandList.push(command);
}
} else if (numberOfPasses === 1) {
passes[0].commandList.push(command);
} else {
var wasVisible = false;
// Loop over cascades from largest to smallest
for (var j = numberOfPasses - 1; j >= 0; --j) {
var cascadeVolume = passes[j].cullingVolume;
if (isVisible(command, cascadeVolume)) {
passes[j].commandList.push(command);
wasVisible = true;
} else if (wasVisible) {
// If it was visible in the previous cascade but now isn't
// then there is no need to check any more cascades
break;
}
}
}
}
}
}
}
function executeShadowMapCastCommands(scene) {
var frameState = scene.frameState;
var shadowMaps = frameState.shadowHints.shadowMaps;
var shadowMapLength = shadowMaps.length;
if (!frameState.shadowHints.shadowsEnabled) {
return;
}
var context = scene.context;
var uniformState = context.uniformState;
for (var i = 0; i < shadowMapLength; ++i) {
var shadowMap = shadowMaps[i];
if (shadowMap.outOfView) {
continue;
}
// Reset the command lists
var j;
var passes = shadowMap.passes;
var numberOfPasses = passes.length;
for (j = 0; j < numberOfPasses; ++j) {
passes[j].commandList.length = 0;
}
// Insert the primitive/model commands into the command lists
var sceneCommands = scene.frameState.commandList;
insertShadowCastCommands(scene, sceneCommands, shadowMap);
for (j = 0; j < numberOfPasses; ++j) {
var pass = shadowMap.passes[j];
uniformState.updateCamera(pass.camera);
shadowMap.updatePass(context, j);
var numberOfCommands = pass.commandList.length;
for (var k = 0; k < numberOfCommands; ++k) {
var command = pass.commandList[k];
// Set the correct pass before rendering into the shadow map because some shaders
// conditionally render based on whether the pass is translucent or opaque.
uniformState.updatePass(command.pass);
executeCommand(command.derivedCommands.shadows.castCommands[i], scene, context, pass.passState);
}
}
}
}
function updateAndExecuteCommands(scene, passState, backgroundColor, picking) {
var context = scene._context;
var viewport = passState.viewport;
var frameState = scene._frameState;
var camera = frameState.camera;
var mode = frameState.mode;
if (scene._useWebVR && mode !== SceneMode.SCENE2D) {
updatePrimitives(scene);
createPotentiallyVisibleSet(scene);
updateAndClearFramebuffers(scene, passState, backgroundColor, picking);
executeComputeCommands(scene);
executeShadowMapCastCommands(scene);
// Based on Calculating Stereo pairs by Paul Bourke
// http://paulbourke.net/stereographics/stereorender/
viewport.x = 0;
viewport.y = 0;
viewport.width = context.drawingBufferWidth * 0.5;
viewport.height = context.drawingBufferHeight;
var savedCamera = Camera.clone(camera, scene._cameraVR);
var near = camera.frustum.near;
var fo = near * 5.0;
var eyeSeparation = fo / 30.0;
var eyeTranslation = Cartesian3.multiplyByScalar(savedCamera.right, eyeSeparation * 0.5, scratchEyeTranslation);
camera.frustum.aspectRatio = viewport.width / viewport.height;
var offset = 0.5 * eyeSeparation * near / fo;
Cartesian3.add(savedCamera.position, eyeTranslation, camera.position);
camera.frustum.xOffset = offset;
executeCommands(scene, passState);
viewport.x = passState.viewport.width;
Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position);
camera.frustum.xOffset = -offset;
executeCommands(scene, passState);
Camera.clone(savedCamera, camera);
} else {
viewport.x = 0;
viewport.y = 0;
viewport.width = context.drawingBufferWidth;
viewport.height = context.drawingBufferHeight;
if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) {
executeCommandsInViewport(true, scene, passState, backgroundColor, picking);
} else {
execute2DViewportCommands(scene, passState, backgroundColor, picking);
}
}
}
var scratch2DViewportCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
var scratch2DViewportMaxCoord = new Cartesian3();
var scratch2DViewportSavedPosition = new Cartesian3();
var scratch2DViewportTransform = new Matrix4();
var scratch2DViewportCameraTransform = new Matrix4();
var scratch2DViewportEyePoint = new Cartesian3();
var scratch2DViewportWindowCoords = new Cartesian3();
function execute2DViewportCommands(scene, passState, backgroundColor, picking) {
var context = scene.context;
var frameState = scene.frameState;
var camera = scene.camera;
var viewport = passState.viewport;
var maxCartographic = scratch2DViewportCartographic;
var maxCoord = scratch2DViewportMaxCoord;
var projection = scene.mapProjection;
projection.project(maxCartographic, maxCoord);
var position = Cartesian3.clone(camera.position, scratch2DViewportSavedPosition);
var transform = Matrix4.clone(camera.transform, scratch2DViewportCameraTransform);
var frustum = camera.frustum.clone();
camera._setTransform(Matrix4.IDENTITY);
var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, scratch2DViewportTransform);
var projectionMatrix = camera.frustum.projectionMatrix;
var x = camera.positionWC.y;
var eyePoint = Cartesian3.fromElements(CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x, scratch2DViewportEyePoint);
var windowCoordinates = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, eyePoint, scratch2DViewportWindowCoords);
windowCoordinates.x = Math.floor(windowCoordinates.x);
var viewportX = viewport.x;
var viewportWidth = viewport.width;
if (x === 0.0 || windowCoordinates.x <= 0.0 || windowCoordinates.x >= context.drawingBufferWidth) {
executeCommandsInViewport(true, scene, passState, backgroundColor, picking);
} else if (Math.abs(context.drawingBufferWidth * 0.5 - windowCoordinates.x) < 1.0) {
viewport.width = windowCoordinates.x;
camera.position.x *= CesiumMath.sign(camera.position.x);
camera.frustum.right = 0.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
context.uniformState.update(frameState);
executeCommandsInViewport(true, scene, passState, backgroundColor, picking);
viewport.x = viewport.width;
camera.position.x = -camera.position.x;
camera.frustum.right = -camera.frustum.left;
camera.frustum.left = 0.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
context.uniformState.update(frameState);
executeCommandsInViewport(false, scene, passState, backgroundColor, picking);
} else if (windowCoordinates.x > context.drawingBufferWidth * 0.5) {
viewport.width = windowCoordinates.x;
var right = camera.frustum.right;
camera.frustum.right = maxCoord.x - x;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
context.uniformState.update(frameState);
executeCommandsInViewport(true, scene, passState, backgroundColor, picking);
viewport.x += windowCoordinates.x;
viewport.width = context.drawingBufferWidth - windowCoordinates.x;
camera.position.x = -camera.position.x;
camera.frustum.left = -camera.frustum.right;
camera.frustum.right = right - camera.frustum.right * 2.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
context.uniformState.update(frameState);
executeCommandsInViewport(false, scene, passState, backgroundColor, picking);
} else {
viewport.x = windowCoordinates.x;
viewport.width = context.drawingBufferWidth - windowCoordinates.x;
var left = camera.frustum.left;
camera.frustum.left = -maxCoord.x - x;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
context.uniformState.update(frameState);
executeCommandsInViewport(true, scene, passState, backgroundColor, picking);
viewport.x = 0;
viewport.width = windowCoordinates.x;
camera.position.x = -camera.position.x;
camera.frustum.right = -camera.frustum.left;
camera.frustum.left = left - camera.frustum.left * 2.0;
frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
context.uniformState.update(frameState);
executeCommandsInViewport(false, scene, passState, backgroundColor, picking);
}
camera._setTransform(transform);
Cartesian3.clone(position, camera.position);
camera.frustum = frustum.clone();
viewport.x = viewportX;
viewport.width = viewportWidth;
}
function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor, picking) {
if (!firstViewport) {
scene.frameState.commandList.length = 0;
}
updatePrimitives(scene);
createPotentiallyVisibleSet(scene);
if (firstViewport) {
updateAndClearFramebuffers(scene, passState, backgroundColor, picking);
executeComputeCommands(scene);
executeShadowMapCastCommands(scene);
}
executeCommands(scene, passState);
}
function updateEnvironment(scene) {
var frameState = scene._frameState;
// Update celestial and terrestrial environment effects.
var environmentState = scene._environmentState;
var renderPass = frameState.passes.render;
environmentState.skyBoxCommand = (renderPass && defined(scene.skyBox)) ? scene.skyBox.update(frameState) : undefined;
var skyAtmosphere = scene.skyAtmosphere;
var globe = scene.globe;
if (defined(skyAtmosphere) && defined(globe)) {
skyAtmosphere.setDynamicAtmosphereColor(globe.enableLighting);
environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0;
}
environmentState.skyAtmosphereCommand = (renderPass && defined(skyAtmosphere)) ? skyAtmosphere.update(frameState) : undefined;
var sunCommands = (renderPass && defined(scene.sun)) ? scene.sun.update(scene) : undefined;
environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined;
environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined;
environmentState.moonCommand = (renderPass && defined(scene.moon)) ? scene.moon.update(frameState) : undefined;
var clearGlobeDepth = environmentState.clearGlobeDepth = defined(globe) && (!globe.depthTestAgainstTerrain || scene.mode === SceneMode.SCENE2D);
var useDepthPlane = environmentState.useDepthPlane = clearGlobeDepth && scene.mode === SceneMode.SCENE3D;
if (useDepthPlane) {
// Update the depth plane that is rendered in 3D when the primitives are
// not depth tested against terrain so primitives on the backface
// of the globe are not picked.
scene._depthPlane.update(frameState);
}
}
function updateShadowMaps(scene) {
var frameState = scene._frameState;
var shadowMaps = frameState.shadowMaps;
var length = shadowMaps.length;
var shadowsEnabled = (length > 0) && !frameState.passes.pick && (scene.mode === SceneMode.SCENE3D);
if (shadowsEnabled !== frameState.shadowHints.shadowsEnabled) {
// Update derived commands when shadowsEnabled changes
++frameState.shadowHints.lastDirtyTime;
frameState.shadowHints.shadowsEnabled = shadowsEnabled;
}
if (!shadowsEnabled) {
return;
}
// Check if the shadow maps are different than the shadow maps last frame.
// If so, the derived commands need to be updated.
for (var j = 0; j < length; ++j) {
if (shadowMaps[j] !== frameState.shadowHints.shadowMaps[j]) {
++frameState.shadowHints.lastDirtyTime;
break;
}
}
frameState.shadowHints.shadowMaps.length = 0;
frameState.shadowHints.lightShadowMaps.length = 0;
for (var i = 0; i < length; ++i) {
var shadowMap = shadowMaps[i];
shadowMap.update(frameState);
frameState.shadowHints.shadowMaps.push(shadowMap);
if (shadowMap.fromLightSource) {
frameState.shadowHints.lightShadowMaps.push(shadowMap);
}
if (shadowMap.dirty) {
++frameState.shadowHints.lastDirtyTime;
shadowMap.dirty = false;
}
}
}
function updatePrimitives(scene) {
var frameState = scene._frameState;
scene._groundPrimitives.update(frameState);
scene._primitives.update(frameState);
updateShadowMaps(scene);
if (scene._globe) {
scene._globe.update(frameState);
}
}
function updateAndClearFramebuffers(scene, passState, clearColor, picking) {
var context = scene._context;
var environmentState = scene._environmentState;
var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D;
// Preserve the reference to the original framebuffer.
environmentState.originalFramebuffer = passState.framebuffer;
// Manage sun bloom post-processing effect.
if (defined(scene.sun) && scene.sunBloom !== scene._sunBloom) {
if (scene.sunBloom && !useWebVR) {
scene._sunPostProcess = new SunPostProcess();
} else if(defined(scene._sunPostProcess)){
scene._sunPostProcess = scene._sunPostProcess.destroy();
}
scene._sunBloom = scene.sunBloom;
} else if (!defined(scene.sun) && defined(scene._sunPostProcess)) {
scene._sunPostProcess = scene._sunPostProcess.destroy();
scene._sunBloom = false;
}
// Clear the pass state framebuffer.
var clear = scene._clearColorCommand;
Color.clone(clearColor, clear.color);
clear.execute(context, passState);
// Update globe depth rendering based on the current context and clear the globe depth framebuffer.
var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = !picking && defined(scene._globeDepth);
if (useGlobeDepthFramebuffer) {
scene._globeDepth.update(context);
scene._globeDepth.clear(context, passState, clearColor);
}
// Determine if there are any translucent surfaces in any of the frustums.
var renderTranslucentCommands = false;
var frustumCommandsList = scene._frustumCommandsList;
var numFrustums = frustumCommandsList.length;
for (var i = 0; i < numFrustums; ++i) {
if (frustumCommandsList[i].indices[Pass.TRANSLUCENT] > 0) {
renderTranslucentCommands = true;
break;
}
}
// If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer.
var useOIT = environmentState.useOIT = !picking && renderTranslucentCommands && defined(scene._oit) && scene._oit.isSupported();
if (useOIT) {
scene._oit.update(context, scene._globeDepth.framebuffer);
scene._oit.clear(context, passState, clearColor);
environmentState.useOIT = scene._oit.isSupported();
}
// If supported, configure FXAA to use the globe depth color texture and clear the FXAA framebuffer.
var useFXAA = environmentState.useFXAA = !picking && scene.fxaa;
if (useFXAA) {
scene._fxaa.update(context);
scene._fxaa.clear(context, passState, clearColor);
}
if (environmentState.isSunVisible && scene.sunBloom && !useWebVR) {
passState.framebuffer = scene._sunPostProcess.update(passState);
} else if (useGlobeDepthFramebuffer) {
passState.framebuffer = scene._globeDepth.framebuffer;
} else if (useFXAA) {
passState.framebuffer = scene._fxaa.getColorFramebuffer();
}
if (defined(passState.framebuffer)) {
clear.execute(context, passState);
}
}
function resolveFramebuffers(scene, passState) {
var context = scene._context;
var environmentState = scene._environmentState;
var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer;
if (scene.debugShowGlobeDepth && useGlobeDepthFramebuffer) {
var gd = getDebugGlobeDepth(scene, scene.debugShowDepthFrustum - 1);
gd.executeDebugGlobeDepth(context, passState);
}
if (scene.debugShowPickDepth && useGlobeDepthFramebuffer) {
var pd = getPickDepth(scene, scene.debugShowDepthFrustum - 1);
pd.executeDebugPickDepth(context, passState);
}
var useOIT = environmentState.useOIT;
var useFXAA = environmentState.useFXAA;
if (useOIT) {
passState.framebuffer = useFXAA ? scene._fxaa.getColorFramebuffer() : undefined;
scene._oit.execute(context, passState);
}
if (useFXAA) {
if (!useOIT && useGlobeDepthFramebuffer) {
passState.framebuffer = scene._fxaa.getColorFramebuffer();
scene._globeDepth.executeCopyColor(context, passState);
}
passState.framebuffer = environmentState.originalFramebuffer;
scene._fxaa.execute(context, passState);
}
if (!useOIT && !useFXAA && useGlobeDepthFramebuffer) {
passState.framebuffer = environmentState.originalFramebuffer;
scene._globeDepth.executeCopyColor(context, passState);
}
}
function callAfterRenderFunctions(frameState) {
// Functions are queued up during primitive update and executed here in case
// the function modifies scene state that should remain constant over the frame.
var functions = frameState.afterRender;
for (var i = 0, length = functions.length; i < length; ++i) {
functions[i]();
}
functions.length = 0;
}
/**
* @private
*/
Scene.prototype.initializeFrame = function() {
// Destroy released shaders once every 120 frames to avoid thrashing the cache
if (this._shaderFrameCount++ === 120) {
this._shaderFrameCount = 0;
this._context.shaderCache.destroyReleasedShaderPrograms();
}
this._tweens.update();
this._screenSpaceCameraController.update();
if (defined(this._deviceOrientationCameraController)) {
this._deviceOrientationCameraController.update();
}
this._camera.update(this._mode);
this._camera._updateCameraChanged();
};
var scratchEyeTranslation = new Cartesian3();
function render(scene, time) {
if (!defined(time)) {
time = JulianDate.now();
}
var camera = scene._camera;
if (!cameraEqual(camera, scene._cameraClone, CesiumMath.EPSILON6)) {
if (!scene._cameraStartFired) {
camera.moveStart.raiseEvent();
scene._cameraStartFired = true;
}
scene._cameraMovedTime = getTimestamp();
Camera.clone(camera, scene._cameraClone);
} else if (scene._cameraStartFired && getTimestamp() - scene._cameraMovedTime > scene.cameraEventWaitTime) {
camera.moveEnd.raiseEvent();
scene._cameraStartFired = false;
}
scene._preRender.raiseEvent(scene, time);
var context = scene.context;
var us = context.uniformState;
var frameState = scene._frameState;
var frameNumber = CesiumMath.incrementWrap(frameState.frameNumber, 15000000.0, 1.0);
updateFrameState(scene, frameNumber, time);
frameState.passes.render = true;
frameState.creditDisplay.beginFrame();
scene.fog.update(frameState);
us.update(frameState);
var shadowMap = scene.shadowMap;
if (defined(shadowMap) && shadowMap.enabled) {
// Update the sun's direction
Cartesian3.negate(us.sunDirectionWC, scene._sunCamera.direction);
frameState.shadowMaps.push(shadowMap);
}
scene._computeCommandList.length = 0;
scene._overlayCommandList.length = 0;
var passState = scene._passState;
passState.framebuffer = undefined;
passState.blendingEnabled = undefined;
passState.scissorTest = undefined;
if (defined(scene.globe)) {
scene.globe.beginFrame(frameState);
}
updateEnvironment(scene);
updateAndExecuteCommands(scene, passState, defaultValue(scene.backgroundColor, Color.BLACK));
resolveFramebuffers(scene, passState);
executeOverlayCommands(scene, passState);
if (defined(scene.globe)) {
scene.globe.endFrame(frameState);
}
frameState.creditDisplay.endFrame();
if (scene.debugShowFramesPerSecond) {
if (!defined(scene._performanceDisplay)) {
var performanceContainer = document.createElement('div');
performanceContainer.className = 'cesium-performanceDisplay-defaultContainer';
var container = scene._canvas.parentNode;
container.appendChild(performanceContainer);
var performanceDisplay = new PerformanceDisplay({container: performanceContainer});
scene._performanceDisplay = performanceDisplay;
scene._performanceContainer = performanceContainer;
}
scene._performanceDisplay.update();
} else if (defined(scene._performanceDisplay)) {
scene._performanceDisplay = scene._performanceDisplay && scene._performanceDisplay.destroy();
scene._performanceContainer.parentNode.removeChild(scene._performanceContainer);
}
context.endFrame();
callAfterRenderFunctions(frameState);
scene._postRender.raiseEvent(scene, time);
}
/**
* @private
*/
Scene.prototype.render = function(time) {
try {
render(this, time);
} catch (error) {
this._renderError.raiseEvent(this, error);
if (this.rethrowRenderErrors) {
throw error;
}
}
};
/**
* @private
*/
Scene.prototype.clampLineWidth = function(width) {
return Math.max(ContextLimits.minimumAliasedLineWidth, Math.min(width, ContextLimits.maximumAliasedLineWidth));
};
var orthoPickingFrustum = new OrthographicFrustum();
var scratchOrigin = new Cartesian3();
var scratchDirection = new Cartesian3();
var scratchPixelSize = new Cartesian2();
var scratchPickVolumeMatrix4 = new Matrix4();
function getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height) {
var camera = scene._camera;
var frustum = camera.frustum;
var viewport = scene._passState.viewport;
var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
x *= (frustum.right - frustum.left) * 0.5;
var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
y *= (frustum.top - frustum.bottom) * 0.5;
var transform = Matrix4.clone(camera.transform, scratchPickVolumeMatrix4);
camera._setTransform(Matrix4.IDENTITY);
var origin = Cartesian3.clone(camera.position, scratchOrigin);
Cartesian3.multiplyByScalar(camera.right, x, scratchDirection);
Cartesian3.add(scratchDirection, origin, origin);
Cartesian3.multiplyByScalar(camera.up, y, scratchDirection);
Cartesian3.add(scratchDirection, origin, origin);
camera._setTransform(transform);
Cartesian3.fromElements(origin.z, origin.x, origin.y, origin);
var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize);
var ortho = orthoPickingFrustum;
ortho.right = pixelSize.x * 0.5;
ortho.left = -ortho.right;
ortho.top = pixelSize.y * 0.5;
ortho.bottom = -ortho.top;
ortho.near = frustum.near;
ortho.far = frustum.far;
return ortho.computeCullingVolume(origin, camera.directionWC, camera.upWC);
}
var perspPickingFrustum = new PerspectiveOffCenterFrustum();
function getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height) {
var camera = scene._camera;
var frustum = camera.frustum;
var near = frustum.near;
var tanPhi = Math.tan(frustum.fovy * 0.5);
var tanTheta = frustum.aspectRatio * tanPhi;
var viewport = scene._passState.viewport;
var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
var xDir = x * near * tanTheta;
var yDir = y * near * tanPhi;
var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize);
var pickWidth = pixelSize.x * width * 0.5;
var pickHeight = pixelSize.y * height * 0.5;
var offCenter = perspPickingFrustum;
offCenter.top = yDir + pickHeight;
offCenter.bottom = yDir - pickHeight;
offCenter.right = xDir + pickWidth;
offCenter.left = xDir - pickWidth;
offCenter.near = near;
offCenter.far = frustum.far;
return offCenter.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
}
function getPickCullingVolume(scene, drawingBufferPosition, width, height) {
if (scene._mode === SceneMode.SCENE2D) {
return getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height);
}
return getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height);
}
// pick rectangle width and height, assumed odd
var rectangleWidth = 3.0;
var rectangleHeight = 3.0;
var scratchRectangle = new BoundingRectangle(0.0, 0.0, rectangleWidth, rectangleHeight);
var scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);
var scratchPosition = new Cartesian2();
/**
* Returns an object with a `primitive` property that contains the first (top) primitive in the scene
* at a particular window coordinate or undefined if nothing is at the location. Other properties may
* potentially be set depending on the type of primitive.
*
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @returns {Object} Object containing the picked primitive.
*
* @exception {DeveloperError} windowPosition is undefined.
*/
Scene.prototype.pick = function(windowPosition) {
if(!defined(windowPosition)) {
throw new DeveloperError('windowPosition is undefined.');
}
var context = this._context;
var us = context.uniformState;
var frameState = this._frameState;
var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition);
if (!defined(this._pickFramebuffer)) {
this._pickFramebuffer = context.createPickFramebuffer();
}
// Update with previous frame's number and time, assuming that render is called before picking.
updateFrameState(this, frameState.frameNumber, frameState.time);
frameState.cullingVolume = getPickCullingVolume(this, drawingBufferPosition, rectangleWidth, rectangleHeight);
frameState.passes.pick = true;
us.update(frameState);
scratchRectangle.x = drawingBufferPosition.x - ((rectangleWidth - 1.0) * 0.5);
scratchRectangle.y = (this.drawingBufferHeight - drawingBufferPosition.y) - ((rectangleHeight - 1.0) * 0.5);
var passState = this._pickFramebuffer.begin(scratchRectangle);
updateAndExecuteCommands(this, passState, scratchColorZero, true);
resolveFramebuffers(this, passState);
var object = this._pickFramebuffer.end(scratchRectangle);
context.endFrame();
callAfterRenderFunctions(frameState);
return object;
};
var scratchPackedDepth = new Cartesian4();
var packedDepthScale = new Cartesian4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);
/**
* Returns the cartesian position reconstructed from the depth buffer and window position.
*
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {Cartesian3} [result] The object on which to restore the result.
* @returns {Cartesian3} The cartesian position.
*
* @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported.
* @exception {DeveloperError} 2D is not supported. An orthographic projection matrix is not invertible.
*/
Scene.prototype.pickPosition = function(windowPosition, result) {
if (!this.useDepthPicking) {
return undefined;
}
if(!defined(windowPosition)) {
throw new DeveloperError('windowPosition is undefined.');
}
if (!defined(this._globeDepth)) {
throw new DeveloperError('Picking from the depth buffer is not supported. Check pickPositionSupported.');
}
var context = this._context;
var uniformState = context.uniformState;
var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition);
drawingBufferPosition.y = this.drawingBufferHeight - drawingBufferPosition.y;
var camera = this._camera;
// Create a working frustum from the original camera frustum.
var frustum;
if (defined(camera.frustum.fov)) {
frustum = camera.frustum.clone(scratchPerspectiveFrustum);
} else if (defined(camera.frustum.infiniteProjectionMatrix)){
frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
} else {
throw new DeveloperError('2D is not supported. An orthographic projection matrix is not invertible.');
}
var numFrustums = this.numberOfFrustums;
for (var i = 0; i < numFrustums; ++i) {
var pickDepth = getPickDepth(this, i);
var pixels = context.readPixels({
x : drawingBufferPosition.x,
y : drawingBufferPosition.y,
width : 1,
height : 1,
framebuffer : pickDepth.framebuffer
});
var packedDepth = Cartesian4.unpack(pixels, 0, scratchPackedDepth);
Cartesian4.divideByScalar(packedDepth, 255.0, packedDepth);
var depth = Cartesian4.dot(packedDepth, packedDepthScale);
if (depth > 0.0 && depth < 1.0) {
var renderedFrustum = this._frustumCommandsList[i];
frustum.near = renderedFrustum.near * (i !== 0 ? OPAQUE_FRUSTUM_NEAR_OFFSET : 1.0);
frustum.far = renderedFrustum.far;
uniformState.updateFrustum(frustum);
return SceneTransforms.drawingBufferToWgs84Coordinates(this, drawingBufferPosition, depth, result);
}
}
return undefined;
};
/**
* Returns a list of objects, each containing a `primitive` property, for all primitives at
* a particular window coordinate position. Other properties may also be set depending on the
* type of primitive. The primitives in the list are ordered by their visual order in the
* scene (front to back).
*
* @param {Cartesian2} windowPosition Window coordinates to perform picking on.
* @param {Number} [limit] If supplied, stop drilling after collecting this many picks.
* @returns {Object[]} Array of objects, each containing 1 picked primitives.
*
* @exception {DeveloperError} windowPosition is undefined.
*
* @example
* var pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0));
*/
Scene.prototype.drillPick = function(windowPosition, limit) {
// PERFORMANCE_IDEA: This function calls each primitive's update for each pass. Instead
// we could update the primitive once, and then just execute their commands for each pass,
// and cull commands for picked primitives. e.g., base on the command's owner.
if (!defined(windowPosition)) {
throw new DeveloperError('windowPosition is undefined.');
}
var i;
var attributes;
var result = [];
var pickedPrimitives = [];
var pickedAttributes = [];
if (!defined(limit)) {
limit = Number.MAX_VALUE;
}
var pickedResult = this.pick(windowPosition);
while (defined(pickedResult) && defined(pickedResult.primitive)) {
result.push(pickedResult);
if (0 >= --limit) {
break;
}
var primitive = pickedResult.primitive;
var hasShowAttribute = false;
//If the picked object has a show attribute, use it.
if (typeof primitive.getGeometryInstanceAttributes === 'function') {
if (defined(pickedResult.id)) {
attributes = primitive.getGeometryInstanceAttributes(pickedResult.id);
if (defined(attributes) && defined(attributes.show)) {
hasShowAttribute = true;
attributes.show = ShowGeometryInstanceAttribute.toValue(false, attributes.show);
pickedAttributes.push(attributes);
}
}
}
//Otherwise, hide the entire primitive
if (!hasShowAttribute) {
primitive.show = false;
pickedPrimitives.push(primitive);
}
pickedResult = this.pick(windowPosition);
}
// unhide everything we hid while drill picking
for (i = 0; i < pickedPrimitives.length; ++i) {
pickedPrimitives[i].show = true;
}
for (i = 0; i < pickedAttributes.length; ++i) {
attributes = pickedAttributes[i];
attributes.show = ShowGeometryInstanceAttribute.toValue(true, attributes.show);
}
return result;
};
/**
* Instantly completes an active transition.
*/
Scene.prototype.completeMorph = function(){
this._transitioner.completeMorph();
};
/**
* Asynchronously transitions the scene to 2D.
* @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
*/
Scene.prototype.morphTo2D = function(duration) {
var ellipsoid;
var globe = this.globe;
if (defined(globe)) {
ellipsoid = globe.ellipsoid;
} else {
ellipsoid = this.mapProjection.ellipsoid;
}
duration = defaultValue(duration, 2.0);
this._transitioner.morphTo2D(duration, ellipsoid);
};
/**
* Asynchronously transitions the scene to Columbus View.
* @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
*/
Scene.prototype.morphToColumbusView = function(duration) {
var ellipsoid;
var globe = this.globe;
if (defined(globe)) {
ellipsoid = globe.ellipsoid;
} else {
ellipsoid = this.mapProjection.ellipsoid;
}
duration = defaultValue(duration, 2.0);
this._transitioner.morphToColumbusView(duration, ellipsoid);
};
/**
* Asynchronously transitions the scene to 3D.
* @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
*/
Scene.prototype.morphTo3D = function(duration) {
var ellipsoid;
var globe = this.globe;
if (defined(globe)) {
ellipsoid = globe.ellipsoid;
} else {
ellipsoid = this.mapProjection.ellipsoid;
}
duration = defaultValue(duration, 2.0);
this._transitioner.morphTo3D(duration, ellipsoid);
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see Scene#destroy
*/
Scene.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* scene = scene && scene.destroy();
*
* @see Scene#isDestroyed
*/
Scene.prototype.destroy = function() {
this._tweens.removeAll();
this._computeEngine = this._computeEngine && this._computeEngine.destroy();
this._screenSpaceCameraController = this._screenSpaceCameraController && this._screenSpaceCameraController.destroy();
this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy();
this._pickFramebuffer = this._pickFramebuffer && this._pickFramebuffer.destroy();
this._primitives = this._primitives && this._primitives.destroy();
this._groundPrimitives = this._groundPrimitives && this._groundPrimitives.destroy();
this._globe = this._globe && this._globe.destroy();
this.skyBox = this.skyBox && this.skyBox.destroy();
this.skyAtmosphere = this.skyAtmosphere && this.skyAtmosphere.destroy();
this._debugSphere = this._debugSphere && this._debugSphere.destroy();
this.sun = this.sun && this.sun.destroy();
this._sunPostProcess = this._sunPostProcess && this._sunPostProcess.destroy();
this._depthPlane = this._depthPlane && this._depthPlane.destroy();
this._transitioner.destroy();
if (defined(this._globeDepth)) {
this._globeDepth.destroy();
}
if (defined(this._oit)) {
this._oit.destroy();
}
this._fxaa.destroy();
this._context = this._context && this._context.destroy();
this._frameState.creditDisplay.destroy();
if (defined(this._performanceDisplay)){
this._performanceDisplay = this._performanceDisplay && this._performanceDisplay.destroy();
this._performanceContainer.parentNode.removeChild(this._performanceContainer);
}
return destroyObject(this);
};
return Scene;
});
/*global define*/
define('Scene/SingleTileImageryProvider',[
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/GeographicTilingScheme',
'../Core/loadImage',
'../Core/Rectangle',
'../Core/RuntimeError',
'../Core/TileProviderError',
'../ThirdParty/when'
], function(
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
GeographicTilingScheme,
loadImage,
Rectangle,
RuntimeError,
TileProviderError,
when) {
'use strict';
/**
* Provides a single, top-level imagery tile. The single image is assumed to use a
* {@link GeographicTilingScheme}.
*
* @alias SingleTileImageryProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The url for the tile.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
*
* @see ArcGisMapServerImageryProvider
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see createOpenStreetMapImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
* @see UrlTemplateImageryProvider
*/
function SingleTileImageryProvider(options) {
options = defaultValue(options, {});
var url = options.url;
if (!defined(url)) {
throw new DeveloperError('url is required.');
}
this._url = url;
var proxy = options.proxy;
this._proxy = proxy;
var rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
var tilingScheme = new GeographicTilingScheme({
rectangle : rectangle,
numberOfLevelZeroTilesX : 1,
numberOfLevelZeroTilesY : 1,
ellipsoid : options.ellipsoid
});
this._tilingScheme = tilingScheme;
this._image = undefined;
this._texture = undefined;
this._tileWidth = 0;
this._tileHeight = 0;
this._errorEvent = new Event();
this._ready = false;
this._readyPromise = when.defer();
var imageUrl = url;
if (defined(proxy)) {
imageUrl = proxy.getURL(imageUrl);
}
var credit = options.credit;
if (typeof credit === 'string') {
credit = new Credit(credit);
}
this._credit = credit;
var that = this;
var error;
function success(image) {
that._image = image;
that._tileWidth = image.width;
that._tileHeight = image.height;
that._ready = true;
that._readyPromise.resolve(true);
TileProviderError.handleSuccess(that._errorEvent);
}
function failure(e) {
var message = 'Failed to load image ' + imageUrl + '.';
error = TileProviderError.handleError(
error,
that,
that._errorEvent,
message,
0, 0, 0,
doRequest,
e);
that._readyPromise.reject(new RuntimeError(message));
}
function doRequest() {
when(loadImage(imageUrl), success, failure);
}
doRequest();
}
defineProperties(SingleTileImageryProvider.prototype, {
/**
* Gets the URL of the single, top-level imagery tile.
* @memberof SingleTileImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the proxy used by this provider.
* @memberof SingleTileImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._proxy;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
}
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight: {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
}
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
}
return 0;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
if (!this._ready) {
throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
}
return 0;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
}
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
return this._tilingScheme.rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
if (!this._ready) {
throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
}
return undefined;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof SingleTileImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof SingleTileImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof SingleTileImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link SingleTileImageryProvider#ready} returns true.
* @memberof SingleTileImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof SingleTileImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return true;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
SingleTileImageryProvider.prototype.getTileCredits = function(x, y, level) {
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link SingleTileImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
SingleTileImageryProvider.prototype.requestImage = function(x, y, level) {
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
}
return this._image;
};
/**
* Picking features is not currently supported by this imagery provider, so this function simply returns
* undefined.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
SingleTileImageryProvider.prototype.pickFeatures = function() {
return undefined;
};
return SingleTileImageryProvider;
});
/**
* @license
* Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Modifications made by Analytical Graphics, Inc.
*/
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyAtmosphereFS',[],function() {
'use strict';
return "#ifdef COLOR_CORRECT\n\
uniform vec3 u_hsbShift;\n\
#endif\n\
const float g = -0.95;\n\
const float g2 = g * g;\n\
const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n\
const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n\
varying vec3 v_rayleighColor;\n\
varying vec3 v_mieColor;\n\
varying vec3 v_toCamera;\n\
varying vec3 v_positionEC;\n\
#ifdef COLOR_CORRECT\n\
vec3 rgb2hsb(vec3 rgbColor)\n\
{\n\
vec4 p = mix(vec4(rgbColor.bg, K_RGB2HSB.wz), vec4(rgbColor.gb, K_RGB2HSB.xy), step(rgbColor.b, rgbColor.g));\n\
vec4 q = mix(vec4(p.xyw, rgbColor.r), vec4(rgbColor.r, p.yzx), step(p.x, rgbColor.r));\n\
float d = q.x - min(q.w, q.y);\n\
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x);\n\
}\n\
vec3 hsb2rgb(vec3 hsbColor)\n\
{\n\
vec3 p = abs(fract(hsbColor.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www);\n\
return hsbColor.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsbColor.y);\n\
}\n\
#endif\n\
void main (void)\n\
{\n\
float cosAngle = dot(czm_sunDirectionWC, normalize(v_toCamera)) / length(v_toCamera);\n\
float rayleighPhase = 0.75 * (1.0 + cosAngle * cosAngle);\n\
float miePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + cosAngle * cosAngle) / pow(1.0 + g2 - 2.0 * g * cosAngle, 1.5);\n\
const float exposure = 2.0;\n\
vec3 rgb = rayleighPhase * v_rayleighColor + miePhase * v_mieColor;\n\
rgb = vec3(1.0) - exp(-exposure * rgb);\n\
float l = czm_luminance(rgb);\n\
#ifdef COLOR_CORRECT\n\
vec3 hsb = rgb2hsb(rgb);\n\
hsb.x += u_hsbShift.x;\n\
hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0);\n\
hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0;\n\
rgb = hsb2rgb(hsb);\n\
l = min(l, czm_luminance(rgb));\n\
#endif\n\
gl_FragColor = vec4(rgb, min(smoothstep(0.0, 0.1, l), 1.0) * smoothstep(0.0, 1.0, czm_morphTime));\n\
}\n\
";
});
/**
* @license
* Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Modifications made by Analytical Graphics, Inc.
*/
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyAtmosphereVS',[],function() {
'use strict';
return "attribute vec4 position;\n\
uniform vec4 u_cameraAndRadiiAndDynamicAtmosphereColor;\n\
const float Kr = 0.0025;\n\
const float Kr4PI = Kr * 4.0 * czm_pi;\n\
const float Km = 0.0015;\n\
const float Km4PI = Km * 4.0 * czm_pi;\n\
const float ESun = 15.0;\n\
const float KmESun = Km * ESun;\n\
const float KrESun = Kr * ESun;\n\
const vec3 InvWavelength = vec3(\n\
5.60204474633241,\n\
9.473284437923038,\n\
19.643802610477206);\n\
const float rayleighScaleDepth = 0.25;\n\
const int nSamples = 2;\n\
const float fSamples = 2.0;\n\
varying vec3 v_rayleighColor;\n\
varying vec3 v_mieColor;\n\
varying vec3 v_toCamera;\n\
float scale(float cosAngle)\n\
{\n\
float x = 1.0 - cosAngle;\n\
return rayleighScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n\
}\n\
void main(void)\n\
{\n\
float cameraHeight = u_cameraAndRadiiAndDynamicAtmosphereColor.x;\n\
float outerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.y;\n\
float innerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.z;\n\
vec3 positionV3 = position.xyz;\n\
vec3 ray = positionV3 - czm_viewerPositionWC;\n\
float far = length(ray);\n\
ray /= far;\n\
float atmosphereScale = 1.0 / (outerRadius - innerRadius);\n\
#ifdef SKY_FROM_SPACE\n\
float B = 2.0 * dot(czm_viewerPositionWC, ray);\n\
float C = cameraHeight * cameraHeight - outerRadius * outerRadius;\n\
float det = max(0.0, B*B - 4.0 * C);\n\
float near = 0.5 * (-B - sqrt(det));\n\
vec3 start = czm_viewerPositionWC + ray * near;\n\
far -= near;\n\
float startAngle = dot(ray, start) / outerRadius;\n\
float startDepth = exp(-1.0 / rayleighScaleDepth );\n\
float startOffset = startDepth*scale(startAngle);\n\
#else // SKY_FROM_ATMOSPHERE\n\
vec3 start = czm_viewerPositionWC;\n\
float height = length(start);\n\
float depth = exp((atmosphereScale / rayleighScaleDepth ) * (innerRadius - cameraHeight));\n\
float startAngle = dot(ray, start) / height;\n\
float startOffset = depth*scale(startAngle);\n\
#endif\n\
float sampleLength = far / fSamples;\n\
float scaledLength = sampleLength * atmosphereScale;\n\
vec3 sampleRay = ray * sampleLength;\n\
vec3 samplePoint = start + sampleRay * 0.5;\n\
vec3 frontColor = vec3(0.0, 0.0, 0.0);\n\
vec3 lightDir = (u_cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? czm_sunPositionWC - czm_viewerPositionWC : czm_viewerPositionWC;\n\
lightDir = normalize(lightDir);\n\
for(int i=0; i
* This is only supported in 3D. atmosphere is faded out when morphing to 2D or Columbus view.
*
*
* @alias SkyAtmosphere
* @constructor
*
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid that the atmosphere is drawn around.
*
* @example
* scene.skyAtmosphere = new Cesium.SkyAtmosphere();
*
* @see Scene.skyAtmosphere
*/
function SkyAtmosphere(ellipsoid) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
/**
* Determines if the atmosphere is shown.
*
* @type {Boolean}
* @default true
*/
this.show = true;
this._ellipsoid = ellipsoid;
this._command = new DrawCommand({
owner : this
});
this._spSkyFromSpace = undefined;
this._spSkyFromAtmosphere = undefined;
this._spSkyFromSpaceColorCorrect = undefined;
this._spSkyFromAtmosphereColorCorrect = undefined;
/**
* The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift).
* A hue shift of 1.0 indicates a complete rotation of the hues available.
* @type {Number}
* @default 0.0
*/
this.hueShift = 0.0;
/**
* The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift).
* A saturation shift of -1.0 is monochrome.
* @type {Number}
* @default 0.0
*/
this.saturationShift = 0.0;
/**
* The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift).
* A brightness shift of -1.0 is complete darkness, which will let space show through.
* @type {Number}
* @default 0.0
*/
this.brightnessShift = 0.0;
this._hueSaturationBrightness = new Cartesian3();
// camera height, outer radius, inner radius, dynamic atmosphere color flag
var cameraAndRadiiAndDynamicAtmosphereColor = new Cartesian4();
// Toggles whether the sun position is used. 0 treats the sun as always directly overhead.
cameraAndRadiiAndDynamicAtmosphereColor.w = 0;
cameraAndRadiiAndDynamicAtmosphereColor.y = Cartesian3.maximumComponent(Cartesian3.multiplyByScalar(ellipsoid.radii, 1.025, new Cartesian3()));
cameraAndRadiiAndDynamicAtmosphereColor.z = ellipsoid.maximumRadius;
this._cameraAndRadiiAndDynamicAtmosphereColor = cameraAndRadiiAndDynamicAtmosphereColor;
var that = this;
this._command.uniformMap = {
u_cameraAndRadiiAndDynamicAtmosphereColor : function() {
return that._cameraAndRadiiAndDynamicAtmosphereColor;
},
u_hsbShift : function() {
that._hueSaturationBrightness.x = that.hueShift;
that._hueSaturationBrightness.y = that.saturationShift;
that._hueSaturationBrightness.z = that.brightnessShift;
return that._hueSaturationBrightness;
}
};
}
defineProperties(SkyAtmosphere.prototype, {
/**
* Gets the ellipsoid the atmosphere is drawn around.
* @memberof SkyAtmosphere.prototype
*
* @type {Ellipsoid}
* @readonly
*/
ellipsoid : {
get : function() {
return this._ellipsoid;
}
}
});
/**
* @private
*/
SkyAtmosphere.prototype.setDynamicAtmosphereColor = function(enableLighting) {
this._cameraAndRadiiAndDynamicAtmosphereColor.w = enableLighting ? 1 : 0;
};
/**
* @private
*/
SkyAtmosphere.prototype.update = function(frameState) {
if (!this.show) {
return undefined;
}
if ((frameState.mode !== SceneMode.SCENE3D) &&
(frameState.mode !== SceneMode.MORPHING)) {
return undefined;
}
// The atmosphere is only rendered during the render pass; it is not pickable, it doesn't cast shadows, etc.
if (!frameState.passes.render) {
return undefined;
}
var command = this._command;
if (!defined(command.vertexArray)) {
var context = frameState.context;
var geometry = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
radii : Cartesian3.multiplyByScalar(this._ellipsoid.radii, 1.025, new Cartesian3()),
slicePartitions : 256,
stackPartitions : 256,
vertexFormat : VertexFormat.POSITION_ONLY
}));
command.vertexArray = VertexArray.fromGeometry({
context : context,
geometry : geometry,
attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
bufferUsage : BufferUsage.STATIC_DRAW
});
command.renderState = RenderState.fromCache({
cull : {
enabled : true,
face : CullFace.FRONT
},
blending : BlendingState.ALPHA_BLEND
});
var vs = new ShaderSource({
defines : ['SKY_FROM_SPACE'],
sources : [SkyAtmosphereVS]
});
this._spSkyFromSpace = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : SkyAtmosphereFS
});
vs = new ShaderSource({
defines : ['SKY_FROM_ATMOSPHERE'],
sources : [SkyAtmosphereVS]
});
this._spSkyFromAtmosphere = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : SkyAtmosphereFS
});
}
// Compile the color correcting versions of the shader on demand
var useColorCorrect = colorCorrect(this);
if (useColorCorrect && (!defined(this._spSkyFromSpaceColorCorrect) || !defined(this._spSkyFromAtmosphereColorCorrect))) {
var contextColorCorrect = frameState.context;
var vsColorCorrect = new ShaderSource({
defines : ['SKY_FROM_SPACE'],
sources : [SkyAtmosphereVS]
});
var fsColorCorrect = new ShaderSource({
defines : ['COLOR_CORRECT'],
sources : [SkyAtmosphereFS]
});
this._spSkyFromSpaceColorCorrect = ShaderProgram.fromCache({
context : contextColorCorrect,
vertexShaderSource : vsColorCorrect,
fragmentShaderSource : fsColorCorrect
});
vsColorCorrect = new ShaderSource({
defines : ['SKY_FROM_ATMOSPHERE'],
sources : [SkyAtmosphereVS]
});
this._spSkyFromAtmosphereColorCorrect = ShaderProgram.fromCache({
context : contextColorCorrect,
vertexShaderSource : vsColorCorrect,
fragmentShaderSource : fsColorCorrect
});
}
var cameraPosition = frameState.camera.positionWC;
var cameraHeight = Cartesian3.magnitude(cameraPosition);
this._cameraAndRadiiAndDynamicAtmosphereColor.x = cameraHeight;
if (cameraHeight > this._cameraAndRadiiAndDynamicAtmosphereColor.y) {
// Camera in space
command.shaderProgram = useColorCorrect ? this._spSkyFromSpaceColorCorrect : this._spSkyFromSpace;
} else {
// Camera in atmosphere
command.shaderProgram = useColorCorrect ? this._spSkyFromAtmosphereColorCorrect : this._spSkyFromAtmosphere;
}
return command;
};
function colorCorrect(skyAtmosphere) {
return !(CesiumMath.equalsEpsilon(skyAtmosphere.hueShift, 0.0, CesiumMath.EPSILON7) &&
CesiumMath.equalsEpsilon(skyAtmosphere.saturationShift, 0.0, CesiumMath.EPSILON7) &&
CesiumMath.equalsEpsilon(skyAtmosphere.brightnessShift, 0.0, CesiumMath.EPSILON7));
}
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see SkyAtmosphere#destroy
*/
SkyAtmosphere.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* skyAtmosphere = skyAtmosphere && skyAtmosphere.destroy();
*
* @see SkyAtmosphere#isDestroyed
*/
SkyAtmosphere.prototype.destroy = function() {
var command = this._command;
command.vertexArray = command.vertexArray && command.vertexArray.destroy();
this._spSkyFromSpace = this._spSkyFromSpace && this._spSkyFromSpace.destroy();
this._spSkyFromAtmosphere = this._spSkyFromAtmosphere && this._spSkyFromAtmosphere.destroy();
this._spSkyFromSpaceColorCorrect = this._spSkyFromSpaceColorCorrect && this._spSkyFromSpaceColorCorrect.destroy();
this._spSkyFromAtmosphereColorCorrect = this._spSkyFromAtmosphereColorCorrect && this._spSkyFromAtmosphereColorCorrect.destroy();
return destroyObject(this);
};
return SkyAtmosphere;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyBoxFS',[],function() {
'use strict';
return "uniform samplerCube u_cubeMap;\n\
varying vec3 v_texCoord;\n\
void main()\n\
{\n\
vec3 rgb = textureCube(u_cubeMap, normalize(v_texCoord)).rgb;\n\
gl_FragColor = vec4(rgb, czm_morphTime);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyBoxVS',[],function() {
'use strict';
return "attribute vec3 position;\n\
varying vec3 v_texCoord;\n\
void main()\n\
{\n\
vec3 p = czm_viewRotation * (czm_temeToPseudoFixed * (czm_entireFrustum.y * position));\n\
gl_Position = czm_projection * vec4(p, 1.0);\n\
v_texCoord = position.xyz;\n\
}\n\
";
});
/*global define*/
define('Scene/SkyBox',[
'../Core/BoxGeometry',
'../Core/Cartesian3',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/GeometryPipeline',
'../Core/Matrix4',
'../Core/VertexFormat',
'../Renderer/BufferUsage',
'../Renderer/CubeMap',
'../Renderer/DrawCommand',
'../Renderer/loadCubeMap',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/VertexArray',
'../Shaders/SkyBoxFS',
'../Shaders/SkyBoxVS',
'./BlendingState',
'./SceneMode'
], function(
BoxGeometry,
Cartesian3,
defaultValue,
defined,
destroyObject,
DeveloperError,
GeometryPipeline,
Matrix4,
VertexFormat,
BufferUsage,
CubeMap,
DrawCommand,
loadCubeMap,
RenderState,
ShaderProgram,
VertexArray,
SkyBoxFS,
SkyBoxVS,
BlendingState,
SceneMode) {
'use strict';
/**
* A sky box around the scene to draw stars. The sky box is defined using the True Equator Mean Equinox (TEME) axes.
*
* This is only supported in 3D. The sky box is faded out when morphing to 2D or Columbus view. The size of
* the sky box must not exceed {@link Scene#maximumCubeMapSize}.
*
*
* @alias SkyBox
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {Object} [options.sources] The source URL or Image
object for each of the six cube map faces. See the example below.
* @param {Boolean} [options.show=true] Determines if this primitive will be shown.
*
*
* @example
* scene.skyBox = new Cesium.SkyBox({
* sources : {
* positiveX : 'skybox_px.png',
* negativeX : 'skybox_nx.png',
* positiveY : 'skybox_py.png',
* negativeY : 'skybox_ny.png',
* positiveZ : 'skybox_pz.png',
* negativeZ : 'skybox_nz.png'
* }
* });
*
* @see Scene#skyBox
* @see Transforms.computeTemeToPseudoFixedMatrix
*/
function SkyBox(options) {
/**
* The sources used to create the cube map faces: an object
* with positiveX
, negativeX
, positiveY
,
* negativeY
, positiveZ
, and negativeZ
properties.
* These can be either URLs or Image
objects.
*
* @type Object
* @default undefined
*/
this.sources = options.sources;
this._sources = undefined;
/**
* Determines if the sky box will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = defaultValue(options.show, true);
this._command = new DrawCommand({
modelMatrix : Matrix4.clone(Matrix4.IDENTITY),
owner : this
});
this._cubeMap = undefined;
}
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {DeveloperError} this.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.
* @exception {DeveloperError} this.sources properties must all be the same type.
*/
SkyBox.prototype.update = function(frameState) {
if (!this.show) {
return undefined;
}
if ((frameState.mode !== SceneMode.SCENE3D) &&
(frameState.mode !== SceneMode.MORPHING)) {
return undefined;
}
// The sky box is only rendered during the render pass; it is not pickable, it doesn't cast shadows, etc.
if (!frameState.passes.render) {
return undefined;
}
var context = frameState.context;
if (this._sources !== this.sources) {
this._sources = this.sources;
var sources = this.sources;
if ((!defined(sources.positiveX)) ||
(!defined(sources.negativeX)) ||
(!defined(sources.positiveY)) ||
(!defined(sources.negativeY)) ||
(!defined(sources.positiveZ)) ||
(!defined(sources.negativeZ))) {
throw new DeveloperError('this.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.');
}
if ((typeof sources.positiveX !== typeof sources.negativeX) ||
(typeof sources.positiveX !== typeof sources.positiveY) ||
(typeof sources.positiveX !== typeof sources.negativeY) ||
(typeof sources.positiveX !== typeof sources.positiveZ) ||
(typeof sources.positiveX !== typeof sources.negativeZ)) {
throw new DeveloperError('this.sources properties must all be the same type.');
}
if (typeof sources.positiveX === 'string') {
// Given urls for cube-map images. Load them.
loadCubeMap(context, this._sources).then(function(cubeMap) {
that._cubeMap = that._cubeMap && that._cubeMap.destroy();
that._cubeMap = cubeMap;
});
} else {
this._cubeMap = this._cubeMap && this._cubeMap.destroy();
this._cubeMap = new CubeMap({
context : context,
source : sources
});
}
}
var command = this._command;
if (!defined(command.vertexArray)) {
var that = this;
command.uniformMap = {
u_cubeMap: function() {
return that._cubeMap;
}
};
var geometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
dimensions : new Cartesian3(2.0, 2.0, 2.0),
vertexFormat : VertexFormat.POSITION_ONLY
}));
var attributeLocations = GeometryPipeline.createAttributeLocations(geometry);
command.vertexArray = VertexArray.fromGeometry({
context : context,
geometry : geometry,
attributeLocations : attributeLocations,
bufferUsage : BufferUsage.STATIC_DRAW
});
command.shaderProgram = ShaderProgram.fromCache({
context : context,
vertexShaderSource : SkyBoxVS,
fragmentShaderSource : SkyBoxFS,
attributeLocations : attributeLocations
});
command.renderState = RenderState.fromCache({
blending : BlendingState.ALPHA_BLEND
});
}
if (!defined(this._cubeMap)) {
return undefined;
}
return command;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see SkyBox#destroy
*/
SkyBox.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* skyBox = skyBox && skyBox.destroy();
*
* @see SkyBox#isDestroyed
*/
SkyBox.prototype.destroy = function() {
var command = this._command;
command.vertexArray = command.vertexArray && command.vertexArray.destroy();
command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy();
this._cubeMap = this._cubeMap && this._cubeMap.destroy();
return destroyObject(this);
};
return SkyBox;
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SunFS',[],function() {
'use strict';
return "uniform sampler2D u_texture;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SunTextureFS',[],function() {
'use strict';
return "uniform float u_glowLengthTS;\n\
uniform float u_radiusTS;\n\
varying vec2 v_textureCoordinates;\n\
vec2 rotate(vec2 p, vec2 direction)\n\
{\n\
return vec2(p.x * direction.x - p.y * direction.y, p.x * direction.y + p.y * direction.x);\n\
}\n\
vec4 addBurst(vec2 position, vec2 direction)\n\
{\n\
vec2 rotatedPosition = rotate(position, direction) * vec2(25.0, 0.75);\n\
float radius = length(rotatedPosition);\n\
float burst = 1.0 - smoothstep(0.0, 0.55, radius);\n\
return vec4(burst);\n\
}\n\
void main()\n\
{\n\
vec2 position = v_textureCoordinates - vec2(0.5);\n\
float radius = length(position);\n\
float surface = step(radius, u_radiusTS);\n\
vec4 color = vec4(1.0, 1.0, surface + 0.2, surface);\n\
float glow = 1.0 - smoothstep(0.0, 0.55, radius);\n\
color.ba += mix(vec2(0.0), vec2(1.0), glow) * 0.75;\n\
vec4 burst = vec4(0.0);\n\
burst += 0.4 * addBurst(position, vec2(0.38942, 0.92106));\n\
burst += 0.4 * addBurst(position, vec2(0.99235, 0.12348));\n\
burst += 0.4 * addBurst(position, vec2(0.60327, -0.79754));\n\
burst += 0.3 * addBurst(position, vec2(0.31457, 0.94924));\n\
burst += 0.3 * addBurst(position, vec2(0.97931, 0.20239));\n\
burst += 0.3 * addBurst(position, vec2(0.66507, -0.74678));\n\
color += clamp(burst, vec4(0.0), vec4(1.0)) * 0.15;\n\
gl_FragColor = clamp(color, vec4(0.0), vec4(1.0));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SunVS',[],function() {
'use strict';
return "attribute vec2 direction;\n\
uniform float u_size;\n\
varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
vec4 position;\n\
if (czm_morphTime == 1.0)\n\
{\n\
position = vec4(czm_sunPositionWC, 1.0);\n\
}\n\
else\n\
{\n\
position = vec4(czm_sunPositionColumbusView.zxy, 1.0);\n\
}\n\
vec4 positionEC = czm_view * position;\n\
vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
vec2 halfSize = vec2(u_size * 0.5);\n\
halfSize *= ((direction * 2.0) - 1.0);\n\
gl_Position = czm_viewportOrthographic * vec4(positionWC.xy + halfSize, -positionWC.z, 1.0);\n\
v_textureCoordinates = direction;\n\
}\n\
";
});
/*global define*/
define('Scene/Sun',[
'../Core/BoundingSphere',
'../Core/Cartesian2',
'../Core/Cartesian3',
'../Core/Cartesian4',
'../Core/ComponentDatatype',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/IndexDatatype',
'../Core/Math',
'../Core/Matrix4',
'../Core/PixelFormat',
'../Core/PrimitiveType',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/ComputeCommand',
'../Renderer/DrawCommand',
'../Renderer/RenderState',
'../Renderer/ShaderProgram',
'../Renderer/Texture',
'../Renderer/VertexArray',
'../Shaders/SunFS',
'../Shaders/SunTextureFS',
'../Shaders/SunVS',
'./BlendingState',
'./SceneMode',
'./SceneTransforms'
], function(
BoundingSphere,
Cartesian2,
Cartesian3,
Cartesian4,
ComponentDatatype,
defined,
defineProperties,
destroyObject,
IndexDatatype,
CesiumMath,
Matrix4,
PixelFormat,
PrimitiveType,
Buffer,
BufferUsage,
ComputeCommand,
DrawCommand,
RenderState,
ShaderProgram,
Texture,
VertexArray,
SunFS,
SunTextureFS,
SunVS,
BlendingState,
SceneMode,
SceneTransforms) {
'use strict';
/**
* Draws a sun billboard.
* This is only supported in 3D and Columbus view.
*
* @alias Sun
* @constructor
*
*
* @example
* scene.sun = new Cesium.Sun();
*
* @see Scene#sun
*/
function Sun() {
/**
* Determines if the sun will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = true;
this._drawCommand = new DrawCommand({
primitiveType : PrimitiveType.TRIANGLES,
boundingVolume : new BoundingSphere(),
owner : this
});
this._commands = {
drawCommand : this._drawCommand,
computeCommand : undefined
};
this._boundingVolume = new BoundingSphere();
this._boundingVolume2D = new BoundingSphere();
this._texture = undefined;
this._drawingBufferWidth = undefined;
this._drawingBufferHeight = undefined;
this._radiusTS = undefined;
this._size = undefined;
this.glowFactor = 1.0;
this._glowFactorDirty = false;
var that = this;
this._uniformMap = {
u_texture : function() {
return that._texture;
},
u_size : function() {
return that._size;
}
};
}
defineProperties(Sun.prototype, {
/**
* Gets or sets a number that controls how "bright" the Sun's lens flare appears
* to be. Zero shows just the Sun's disc without any flare.
* Use larger values for a more pronounced flare around the Sun.
*
* @memberof Sun.prototype
* @type {Number}
* @default 1.0
*/
glowFactor : {
get : function () { return this._glowFactor; },
set : function (glowFactor) {
glowFactor = Math.max(glowFactor, 0.0);
this._glowFactor = glowFactor;
this._glowFactorDirty = true;
}
}
});
var scratchPositionWC = new Cartesian2();
var scratchLimbWC = new Cartesian2();
var scratchPositionEC = new Cartesian4();
var scratchCartesian4 = new Cartesian4();
/**
* @private
*/
Sun.prototype.update = function(scene) {
var passState = scene._passState;
var frameState = scene.frameState;
var context = scene.context;
if (!this.show) {
return undefined;
}
var mode = frameState.mode;
if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
return undefined;
}
if (!frameState.passes.render) {
return undefined;
}
var drawingBufferWidth = passState.viewport.width;
var drawingBufferHeight = passState.viewport.height;
if (!defined(this._texture) ||
drawingBufferWidth !== this._drawingBufferWidth ||
drawingBufferHeight !== this._drawingBufferHeight ||
this._glowFactorDirty) {
this._texture = this._texture && this._texture.destroy();
this._drawingBufferWidth = drawingBufferWidth;
this._drawingBufferHeight = drawingBufferHeight;
this._glowFactorDirty = false;
var size = Math.max(drawingBufferWidth, drawingBufferHeight);
size = Math.pow(2.0, Math.ceil(Math.log(size) / Math.log(2.0)) - 2.0);
// The size computed above can be less than 1.0 if size < 4.0. This will probably
// never happen in practice, but does in the tests. Clamp to 1.0 to prevent WebGL
// errors in the tests.
size = Math.max(1.0, size);
this._texture = new Texture({
context : context,
width : size,
height : size,
pixelFormat : PixelFormat.RGBA
});
this._glowLengthTS = this._glowFactor * 5.0;
this._radiusTS = (1.0 / (1.0 + 2.0 * this._glowLengthTS)) * 0.5;
var that = this;
var uniformMap = {
u_glowLengthTS : function() {
return that._glowLengthTS;
},
u_radiusTS : function() {
return that._radiusTS;
}
};
this._commands.computeCommand = new ComputeCommand({
fragmentShaderSource : SunTextureFS,
outputTexture : this._texture,
uniformMap : uniformMap,
persists : false,
owner : this,
postExecute : function() {
that._commands.computeCommand = undefined;
}
});
}
var drawCommand = this._drawCommand;
if (!defined(drawCommand.vertexArray)) {
var attributeLocations = {
direction : 0
};
var directions = new Uint8Array(4 * 2);
directions[0] = 0;
directions[1] = 0;
directions[2] = 255;
directions[3] = 0.0;
directions[4] = 255;
directions[5] = 255;
directions[6] = 0.0;
directions[7] = 255;
var vertexBuffer = Buffer.createVertexBuffer({
context : context,
typedArray : directions,
usage : BufferUsage.STATIC_DRAW
});
var attributes = [{
index : attributeLocations.direction,
vertexBuffer : vertexBuffer,
componentsPerAttribute : 2,
normalize : true,
componentDatatype : ComponentDatatype.UNSIGNED_BYTE
}];
// Workaround Internet Explorer 11.0.8 lack of TRIANGLE_FAN
var indexBuffer = Buffer.createIndexBuffer({
context : context,
typedArray : new Uint16Array([0, 1, 2, 0, 2, 3]),
usage : BufferUsage.STATIC_DRAW,
indexDatatype : IndexDatatype.UNSIGNED_SHORT
});
drawCommand.vertexArray = new VertexArray({
context : context,
attributes : attributes,
indexBuffer : indexBuffer
});
drawCommand.shaderProgram = ShaderProgram.fromCache({
context : context,
vertexShaderSource : SunVS,
fragmentShaderSource : SunFS,
attributeLocations : attributeLocations
});
drawCommand.renderState = RenderState.fromCache({
blending : BlendingState.ALPHA_BLEND
});
drawCommand.uniformMap = this._uniformMap;
}
var sunPosition = context.uniformState.sunPositionWC;
var sunPositionCV = context.uniformState.sunPositionColumbusView;
var boundingVolume = this._boundingVolume;
var boundingVolume2D = this._boundingVolume2D;
Cartesian3.clone(sunPosition, boundingVolume.center);
boundingVolume2D.center.x = sunPositionCV.z;
boundingVolume2D.center.y = sunPositionCV.x;
boundingVolume2D.center.z = sunPositionCV.y;
boundingVolume.radius = CesiumMath.SOLAR_RADIUS + CesiumMath.SOLAR_RADIUS * this._glowLengthTS;
boundingVolume2D.radius = boundingVolume.radius;
if (mode === SceneMode.SCENE3D) {
BoundingSphere.clone(boundingVolume, drawCommand.boundingVolume);
} else if (mode === SceneMode.COLUMBUS_VIEW) {
BoundingSphere.clone(boundingVolume2D, drawCommand.boundingVolume);
}
var position = SceneTransforms.computeActualWgs84Position(frameState, sunPosition, scratchCartesian4);
var dist = Cartesian3.magnitude(Cartesian3.subtract(position, scene.camera.position, scratchCartesian4));
var projMatrix = context.uniformState.projection;
var positionEC = scratchPositionEC;
positionEC.x = 0;
positionEC.y = 0;
positionEC.z = -dist;
positionEC.w = 1;
var positionCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4);
var positionWC = SceneTransforms.clipToDrawingBufferCoordinates(passState.viewport, positionCC, scratchPositionWC);
positionEC.x = CesiumMath.SOLAR_RADIUS;
var limbCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4);
var limbWC = SceneTransforms.clipToDrawingBufferCoordinates(passState.viewport, limbCC, scratchLimbWC);
this._size = Math.ceil(Cartesian2.magnitude(Cartesian2.subtract(limbWC, positionWC, scratchCartesian4)));
this._size = 2.0 * this._size * (1.0 + 2.0 * this._glowLengthTS);
return this._commands;
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} true
if this object was destroyed; otherwise, false
.
*
* @see Sun#destroy
*/
Sun.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* sun = sun && sun.destroy();
*
* @see Sun#isDestroyed
*/
Sun.prototype.destroy = function() {
var command = this._drawCommand;
command.vertexArray = command.vertexArray && command.vertexArray.destroy();
command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy();
this._texture = this._texture && this._texture.destroy();
return destroyObject(this);
};
return Sun;
});
/*global define*/
define('Scene/TileCoordinatesImageryProvider',[
'../Core/Color',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/Event',
'../Core/GeographicTilingScheme',
'../ThirdParty/when'
], function(
Color,
defaultValue,
defined,
defineProperties,
Event,
GeographicTilingScheme,
when) {
'use strict';
/**
* An {@link ImageryProvider} that draws a box around every rendered tile in the tiling scheme, and draws
* a label inside it indicating the X, Y, Level coordinates of the tile. This is mostly useful for
* debugging terrain and imagery rendering problems.
*
* @alias TileCoordinatesImageryProvider
* @constructor
*
* @param {Object} [options] Object with the following properties:
* @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme for which to draw tiles.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
* parameter is specified, the WGS84 ellipsoid is used.
* @param {Color} [options.color=Color.YELLOW] The color to draw the tile box and label.
* @param {Number} [options.tileWidth=256] The width of the tile for level-of-detail selection purposes.
* @param {Number} [options.tileHeight=256] The height of the tile for level-of-detail selection purposes.
*/
function TileCoordinatesImageryProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
this._color = defaultValue(options.color, Color.YELLOW);
this._errorEvent = new Event();
this._tileWidth = defaultValue(options.tileWidth, 256);
this._tileHeight = defaultValue(options.tileHeight, 256);
this._readyPromise = when.resolve(true);
}
defineProperties(TileCoordinatesImageryProvider.prototype, {
/**
* Gets the proxy used by this provider.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return undefined;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight: {
get : function() {
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
return undefined;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
return undefined;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
return this._tilingScheme.rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
return undefined;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return true;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return undefined;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. Setting this property to false reduces memory usage
* and texture upload time.
* @memberof TileCoordinatesImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return true;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
TileCoordinatesImageryProvider.prototype.getTileCredits = function(x, y, level) {
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*/
TileCoordinatesImageryProvider.prototype.requestImage = function(x, y, level) {
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
var context = canvas.getContext('2d');
var cssColor = this._color.toCssColorString();
context.strokeStyle = cssColor;
context.lineWidth = 2;
context.strokeRect(1, 1, 255, 255);
var label = 'L' + level + 'X' + x + 'Y' + y;
context.font = 'bold 25px Arial';
context.textAlign = 'center';
context.fillStyle = 'black';
context.fillText(label, 127, 127);
context.fillStyle = cssColor;
context.fillText(label, 124, 124);
return canvas;
};
/**
* Picking features is not currently supported by this imagery provider, so this function simply returns
* undefined.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
TileCoordinatesImageryProvider.prototype.pickFeatures = function() {
return undefined;
};
return TileCoordinatesImageryProvider;
});
/*global define*/
define('Scene/TileDiscardPolicy',[
'../Core/DeveloperError'
], function(
DeveloperError) {
'use strict';
/**
* A policy for discarding tile images according to some criteria. This type describes an
* interface and is not intended to be instantiated directly.
*
* @alias TileDiscardPolicy
* @constructor
*
* @see DiscardMissingTileImagePolicy
* @see NeverTileDiscardPolicy
*/
function TileDiscardPolicy(options) {
DeveloperError.throwInstantiationError();
}
/**
* Determines if the discard policy is ready to process images.
* @function
*
* @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
*/
TileDiscardPolicy.prototype.isReady = DeveloperError.throwInstantiationError;
/**
* Given a tile image, decide whether to discard that image.
* @function
*
* @param {Image} image An image to test.
* @returns {Boolean} True if the image should be discarded; otherwise, false.
*/
TileDiscardPolicy.prototype.shouldDiscardImage = DeveloperError.throwInstantiationError;
return TileDiscardPolicy;
});
/*global define*/
define('Scene/TileState',[
'../Core/freezeObject'
], function(
freezeObject) {
'use strict';
/**
* @private
*/
var TileState = {
START : 0,
LOADING : 1,
READY : 2,
UPSAMPLED_ONLY : 3
};
return freezeObject(TileState);
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ViewportQuadFS',[],function() {
'use strict';
return "varying vec2 v_textureCoordinates;\n\
void main()\n\
{\n\
czm_materialInput materialInput;\n\
materialInput.s = v_textureCoordinates.s;\n\
materialInput.st = v_textureCoordinates;\n\
materialInput.str = vec3(v_textureCoordinates, 0.0);\n\
materialInput.normalEC = vec3(0.0, 0.0, -1.0);\n\
czm_material material = czm_getMaterial(materialInput);\n\
gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
}\n\
";
});
/*global define*/
define('Scene/ViewportQuad',[
'../Core/BoundingRectangle',
'../Core/Color',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Renderer/Pass',
'../Renderer/RenderState',
'../Renderer/ShaderSource',
'../Shaders/ViewportQuadFS',
'./BlendingState',
'./Material'
], function(
BoundingRectangle,
Color,
defined,
destroyObject,
DeveloperError,
Pass,
RenderState,
ShaderSource,
ViewportQuadFS,
BlendingState,
Material) {
'use strict';
/**
* A viewport aligned quad.
*
* @alias ViewportQuad
* @constructor
*
* @param {BoundingRectangle} [rectangle] The {@link BoundingRectangle} defining the quad's position within the viewport.
* @param {Material} [material] The {@link Material} defining the surface appearance of the viewport quad.
*
* @example
* var viewportQuad = new Cesium.ViewportQuad(new Cesium.BoundingRectangle(0, 0, 80, 40));
* viewportQuad.material.uniforms.color = new Cesium.Color(1.0, 0.0, 0.0, 1.0);
*/
function ViewportQuad(rectangle, material) {
/**
* Determines if the viewport quad primitive will be shown.
*
* @type {Boolean}
* @default true
*/
this.show = true;
if (!defined(rectangle)) {
rectangle = new BoundingRectangle();
}
/**
* The BoundingRectangle defining the quad's position within the viewport.
*
* @type {BoundingRectangle}
*
* @example
* viewportQuad.rectangle = new Cesium.BoundingRectangle(0, 0, 80, 40);
*/
this.rectangle = BoundingRectangle.clone(rectangle);
if (!defined(material)) {
material = Material.fromType(Material.ColorType, {
color : new Color(1.0, 1.0, 1.0, 1.0)
});
}
/**
* The surface appearance of the viewport quad. This can be one of several built-in {@link Material} objects or a custom material, scripted with
* {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
*
* The default material is Material.ColorType
.
*
*
* @type Material
*
* @example
* // 1. Change the color of the default material to yellow
* viewportQuad.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
*
* // 2. Change material to horizontal stripes
* viewportQuad.material = Cesium.Material.fromType(Cesium.Material.StripeType);
*
* @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
*/
this.material = material;
this._material = undefined;
this._overlayCommand = undefined;
this._rs = undefined;
}
/**
* Called when {@link Viewer} or {@link CesiumWidget} render the scene to
* get the draw commands needed to render this primitive.
*
* Do not call this function directly. This is documented just to
* list the exceptions that may be propagated when the scene is rendered:
*
*
* @exception {DeveloperError} this.material must be defined.
* @exception {DeveloperError} this.rectangle must be defined.
*/
ViewportQuad.prototype.update = function(frameState) {
if (!this.show) {
return;
}
if (!defined(this.material)) {
throw new DeveloperError('this.material must be defined.');
}
if (!defined(this.rectangle)) {
throw new DeveloperError('this.rectangle must be defined.');
}
var rs = this._rs;
if ((!defined(rs)) || !BoundingRectangle.equals(rs.viewport, this.rectangle)) {
this._rs = RenderState.fromCache({
blending : BlendingState.ALPHA_BLEND,
viewport : this.rectangle
});
}
var pass = frameState.passes;
if (pass.render) {
var context = frameState.context;
if (this._material !== this.material || !defined(this._overlayCommand)) {
// Recompile shader when material changes
this._material = this.material;
if (defined(this._overlayCommand)) {
this._overlayCommand.shaderProgram.destroy();
}
var fs = new ShaderSource({
sources : [this._material.shaderSource, ViewportQuadFS]
});
this._overlayCommand = context.createViewportQuadCommand(fs, {
renderState : this._rs,
uniformMap : this._material._uniforms,
owner : this
});
this._overlayCommand.pass = Pass.OVERLAY;
}
this._material.update(context);
this._overlayCommand.uniformMap = this._material._uniforms;
frameState.commandList.push(this._overlayCommand);
}
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {Boolean} True if this object was destroyed; otherwise, false.
*
* @see ViewportQuad#destroy
*/
ViewportQuad.prototype.isDestroyed = function() {
return false;
};
/**
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @returns {undefined}
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* quad = quad && quad.destroy();
*
* @see ViewportQuad#isDestroyed
*/
ViewportQuad.prototype.destroy = function() {
if (defined(this._overlayCommand)) {
this._overlayCommand.shaderProgram = this._overlayCommand.shaderProgram && this._overlayCommand.shaderProgram.destroy();
}
return destroyObject(this);
};
return ViewportQuad;
});
/*global define*/
define('Scene/WebMapServiceImageryProvider',[
'../Core/combine',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/freezeObject',
'../Core/GeographicTilingScheme',
'../Core/objectToQuery',
'../Core/queryToObject',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/Uri',
'./GetFeatureInfoFormat',
'./UrlTemplateImageryProvider'
], function(
combine,
defaultValue,
defined,
defineProperties,
DeveloperError,
freezeObject,
GeographicTilingScheme,
objectToQuery,
queryToObject,
WebMercatorTilingScheme,
Uri,
GetFeatureInfoFormat,
UrlTemplateImageryProvider) {
'use strict';
/**
* Provides tiled imagery hosted by a Web Map Service (WMS) server.
*
* @alias WebMapServiceImageryProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The URL of the WMS service. The URL supports the same keywords as the {@link UrlTemplateImageryProvider}.
* @param {String} options.layers The layers to include, separated by commas.
* @param {Object} [options.parameters=WebMapServiceImageryProvider.DefaultParameters] Additional parameters
* to pass to the WMS server in the GetMap URL.
* @param {Object} [options.getFeatureInfoParameters=WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters] Additional
* parameters to pass to the WMS server in the GetFeatureInfo URL.
* @param {Boolean} [options.enablePickFeatures=true] If true, {@link WebMapServiceImageryProvider#pickFeatures} will invoke
* the GetFeatureInfo operation on the WMS server and return the features included in the response. If false,
* {@link WebMapServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
* without communicating with the server. Set this property to false if you know your WMS server does not support
* GetFeatureInfo or if you don't want this provider's features to be pickable. Note that this can be dynamically
* overridden by modifying the WebMapServiceImageryProvider#enablePickFeatures property.
* @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats=WebMapServiceImageryProvider.DefaultGetFeatureInfoFormats] The formats
* in which to try WMS GetFeatureInfo requests.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer.
* @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme to use to divide the world into tiles.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If the tilingScheme is specified,
* this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
* parameter is specified, the WGS84 ellipsoid is used.
* @param {Number} [options.tileWidth=256] The width of each tile in pixels.
* @param {Number} [options.tileHeight=256] The height of each tile in pixels.
* @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when
* specifying this that the number of tiles at the minimum level is small, such as four or less. A larger number is
* likely to result in rendering problems.
* @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
* If not specified, there is no limit.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
* @param {Object} [options.proxy] A proxy to use for requests. This object is
* expected to have a getURL function which returns the proxied URL, if needed.
* @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the {s}
placeholder in the URL template.
* If this parameter is a single string, each character in the string is a subdomain. If it is
* an array, each element in the array is a subdomain.
*
* @see ArcGisMapServerImageryProvider
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see createOpenStreetMapImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapTileServiceImageryProvider
* @see UrlTemplateImageryProvider
*
* @see {@link http://resources.esri.com/help/9.3/arcgisserver/apis/rest/|ArcGIS Server REST API}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
*
* @example
* var provider = new Cesium.WebMapServiceImageryProvider({
* url : 'https://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer',
* layers : '0',
* proxy: new Cesium.DefaultProxy('/proxy/')
* });
*
* viewer.imageryLayers.addImageryProvider(provider);
*/
function WebMapServiceImageryProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
if (!defined(options.layers)) {
throw new DeveloperError('options.layers is required.');
}
this._url = options.url;
this._layers = options.layers;
var getFeatureInfoFormats = defaultValue(options.getFeatureInfoFormats, WebMapServiceImageryProvider.DefaultGetFeatureInfoFormats);
// Build the template URLs for tiles and pickFeatures.
var uri = new Uri(options.url);
var queryOptions = queryToObject(defaultValue(uri.query, ''));
var parameters = combine(objectToLowercase(defaultValue(options.parameters, defaultValue.EMPTY_OBJECT)), WebMapServiceImageryProvider.DefaultParameters);
queryOptions = combine(parameters, queryOptions);
var pickFeaturesUri;
var pickFeaturesQueryOptions;
pickFeaturesUri = new Uri(options.url);
pickFeaturesQueryOptions = queryToObject(defaultValue(pickFeaturesUri.query, ''));
var pickFeaturesParameters = combine(objectToLowercase(defaultValue(options.getFeatureInfoParameters, defaultValue.EMPTY_OBJECT)), WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters);
pickFeaturesQueryOptions = combine(pickFeaturesParameters, pickFeaturesQueryOptions);
function setParameter(name, value) {
if (!defined(queryOptions[name])) {
queryOptions[name] = value;
}
if (defined(pickFeaturesQueryOptions) && !defined(pickFeaturesQueryOptions[name])) {
pickFeaturesQueryOptions[name] = value;
}
}
setParameter('layers', options.layers);
setParameter('srs', options.tilingScheme instanceof WebMercatorTilingScheme ? 'EPSG:3857' : 'EPSG:4326');
setParameter('bbox', '{westProjected},{southProjected},{eastProjected},{northProjected}');
setParameter('width', '{width}');
setParameter('height', '{height}');
uri.query = objectToQuery(queryOptions);
// objectToQuery escapes the placeholders. Undo that.
var templateUrl = uri.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
var pickFeaturesTemplateUrl;
if (defined(pickFeaturesQueryOptions)) {
if (!defined(pickFeaturesQueryOptions.query_layers)) {
pickFeaturesQueryOptions.query_layers = options.layers;
}
if (!defined(pickFeaturesQueryOptions.x)) {
pickFeaturesQueryOptions.x = '{i}';
}
if (!defined(pickFeaturesQueryOptions.y)) {
pickFeaturesQueryOptions.y = '{j}';
}
if (!defined(pickFeaturesQueryOptions.info_format)) {
pickFeaturesQueryOptions.info_format = '{format}';
}
pickFeaturesUri.query = objectToQuery(pickFeaturesQueryOptions);
pickFeaturesTemplateUrl = pickFeaturesUri.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
}
// Let UrlTemplateImageryProvider do the actual URL building.
this._tileProvider = new UrlTemplateImageryProvider({
url : templateUrl,
pickFeaturesUrl : pickFeaturesTemplateUrl,
tilingScheme : defaultValue(options.tilingScheme, new GeographicTilingScheme({ ellipsoid : options.ellipsoid})),
rectangle : options.rectangle,
tileWidth : options.tileWidth,
tileHeight : options.tileHeight,
minimumLevel : options.minimumLevel,
maximumLevel : options.maximumLevel,
proxy : options.proxy,
subdomains: options.subdomains,
tileDiscardPolicy : options.tileDiscardPolicy,
credit : options.credit,
getFeatureInfoFormats : getFeatureInfoFormats,
enablePickFeatures: options.enablePickFeatures
});
}
defineProperties(WebMapServiceImageryProvider.prototype, {
/**
* Gets the URL of the WMS server.
* @memberof WebMapServiceImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the proxy used by this provider.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._tileProvider.proxy;
}
},
/**
* Gets the names of the WMS layers, separated by commas.
* @memberof WebMapServiceImageryProvider.prototype
* @type {String}
* @readonly
*/
layers : {
get : function() {
return this._layers;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
return this._tileProvider.tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight : {
get : function() {
return this._tileProvider.tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
return this._tileProvider.maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
return this._tileProvider.minimumLevel;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
return this._tileProvider.tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
return this._tileProvider.rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
return this._tileProvider.tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._tileProvider.errorEvent;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
get : function() {
return this._tileProvider.ready;
}
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._tileProvider.readyPromise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link WebMapServiceImageryProvider#ready} returns true.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._tileProvider.credit;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return this._tileProvider.hasAlphaChannel;
}
},
/**
* Gets or sets a value indicating whether feature picking is enabled. If true, {@link WebMapServiceImageryProvider#pickFeatures} will
* invoke the GetFeatureInfo
service on the WMS server and attempt to interpret the features included in the response. If false,
* {@link WebMapServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
* features) without communicating with the server. Set this property to false if you know your data
* source does not support picking features or if you don't want this provider's features to be pickable.
* @memberof WebMapServiceImageryProvider.prototype
* @type {Boolean}
* @default true
*/
enablePickFeatures : {
get : function() {
return this._tileProvider.enablePickFeatures;
},
set : function(enablePickFeatures) {
this._tileProvider.enablePickFeatures = enablePickFeatures;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
WebMapServiceImageryProvider.prototype.getTileCredits = function(x, y, level) {
return this._tileProvider.getTileCredits(x, y, level);
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link WebMapServiceImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
WebMapServiceImageryProvider.prototype.requestImage = function(x, y, level) {
return this._tileProvider.requestImage(x, y, level);
};
/**
* Asynchronously determines what features, if any, are located at a given longitude and latitude within
* a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
*
* @exception {DeveloperError} pickFeatures
must not be called before the imagery provider is ready.
*/
WebMapServiceImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
return this._tileProvider.pickFeatures(x, y, level, longitude, latitude);
};
/**
* The default parameters to include in the WMS URL to obtain images. The values are as follows:
* service=WMS
* version=1.1.1
* request=GetMap
* styles=
* format=image/jpeg
*
* @constant
*/
WebMapServiceImageryProvider.DefaultParameters = freezeObject({
service : 'WMS',
version : '1.1.1',
request : 'GetMap',
styles : '',
format : 'image/jpeg'
});
/**
* The default parameters to include in the WMS URL to get feature information. The values are as follows:
* service=WMS
* version=1.1.1
* request=GetFeatureInfo
*
* @constant
*/
WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters = freezeObject({
service : 'WMS',
version : '1.1.1',
request : 'GetFeatureInfo'
});
WebMapServiceImageryProvider.DefaultGetFeatureInfoFormats = freezeObject([
freezeObject(new GetFeatureInfoFormat('json', 'application/json')),
freezeObject(new GetFeatureInfoFormat('xml', 'text/xml')),
freezeObject(new GetFeatureInfoFormat('text', 'text/html'))
]);
function objectToLowercase(obj) {
var result = {};
for ( var key in obj) {
if (obj.hasOwnProperty(key)) {
result[key.toLowerCase()] = obj[key];
}
}
return result;
}
return WebMapServiceImageryProvider;
});
/*global define*/
define('Scene/WebMapTileServiceImageryProvider',[
'../Core/combine',
'../Core/Credit',
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/DeveloperError',
'../Core/Event',
'../Core/freezeObject',
'../Core/isArray',
'../Core/objectToQuery',
'../Core/queryToObject',
'../Core/Rectangle',
'../Core/WebMercatorTilingScheme',
'../ThirdParty/Uri',
'../ThirdParty/when',
'./ImageryProvider'
], function(
combine,
Credit,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
freezeObject,
isArray,
objectToQuery,
queryToObject,
Rectangle,
WebMercatorTilingScheme,
Uri,
when,
ImageryProvider) {
'use strict';
/**
* Provides tiled imagery served by {@link http://www.opengeospatial.org/standards/wmts|WMTS 1.0.0} compliant servers.
* This provider supports HTTP KVP-encoded and RESTful GetTile requests, but does not yet support the SOAP encoding.
*
* @alias WebMapTileServiceImageryProvider
* @constructor
*
* @param {Object} options Object with the following properties:
* @param {String} options.url The base URL for the WMTS GetTile operation (for KVP-encoded requests) or the tile-URL template (for RESTful requests). The tile-URL template should contain the following variables: {style}, {TileMatrixSet}, {TileMatrix}, {TileRow}, {TileCol}. The first two are optional if actual values are hardcoded or not required by the server. The {s} keyword may be used to specify subdomains.
* @param {String} [options.format='image/jpeg'] The MIME type for images to retrieve from the server.
* @param {String} options.layer The layer name for WMTS requests.
* @param {String} options.style The style name for WMTS requests.
* @param {String} options.tileMatrixSetID The identifier of the TileMatrixSet to use for WMTS requests.
* @param {Array} [options.tileMatrixLabels] A list of identifiers in the TileMatrix to use for WMTS requests, one per TileMatrix level.
* @param {Number} [options.tileWidth=256] The tile width in pixels.
* @param {Number} [options.tileHeight=256] The tile height in pixels.
* @param {TilingScheme} [options.tilingScheme] The tiling scheme corresponding to the organization of the tiles in the TileMatrixSet.
* @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
* @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle covered by the layer.
* @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.
* @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
* @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
* @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the {s}
placeholder in the URL template.
* If this parameter is a single string, each character in the string is a subdomain. If it is
* an array, each element in the array is a subdomain.
*
*
* @example
* // Example 1. USGS shaded relief tiles (KVP)
* var shadedRelief1 = new Cesium.WebMapTileServiceImageryProvider({
* url : 'http://basemap.nationalmap.gov/arcgis/rest/services/USGSShadedReliefOnly/MapServer/WMTS',
* layer : 'USGSShadedReliefOnly',
* style : 'default',
* format : 'image/jpeg',
* tileMatrixSetID : 'default028mm',
* // tileMatrixLabels : ['default028mm:0', 'default028mm:1', 'default028mm:2' ...],
* maximumLevel: 19,
* credit : new Cesium.Credit('U. S. Geological Survey')
* });
* viewer.imageryLayers.addImageryProvider(shadedRelief1);
*
* @example
* // Example 2. USGS shaded relief tiles (RESTful)
* var shadedRelief2 = new Cesium.WebMapTileServiceImageryProvider({
* url : 'http://basemap.nationalmap.gov/arcgis/rest/services/USGSShadedReliefOnly/MapServer/WMTS/tile/1.0.0/USGSShadedReliefOnly/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpg',
* layer : 'USGSShadedReliefOnly',
* style : 'default',
* format : 'image/jpeg',
* tileMatrixSetID : 'default028mm',
* maximumLevel: 19,
* credit : new Cesium.Credit('U. S. Geological Survey')
* });
* viewer.imageryLayers.addImageryProvider(shadedRelief2);
*
* @see ArcGisMapServerImageryProvider
* @see BingMapsImageryProvider
* @see GoogleEarthImageryProvider
* @see createOpenStreetMapImageryProvider
* @see SingleTileImageryProvider
* @see createTileMapServiceImageryProvider
* @see WebMapServiceImageryProvider
* @see UrlTemplateImageryProvider
*/
function WebMapTileServiceImageryProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
if (!defined(options.url)) {
throw new DeveloperError('options.url is required.');
}
if (!defined(options.layer)) {
throw new DeveloperError('options.layer is required.');
}
if (!defined(options.style)) {
throw new DeveloperError('options.style is required.');
}
if (!defined(options.tileMatrixSetID)) {
throw new DeveloperError('options.tileMatrixSetID is required.');
}
this._url = options.url;
this._layer = options.layer;
this._style = options.style;
this._tileMatrixSetID = options.tileMatrixSetID;
this._tileMatrixLabels = options.tileMatrixLabels;
this._format = defaultValue(options.format, 'image/jpeg');
this._proxy = options.proxy;
this._tileDiscardPolicy = options.tileDiscardPolicy;
this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
this._tileWidth = defaultValue(options.tileWidth, 256);
this._tileHeight = defaultValue(options.tileHeight, 256);
this._minimumLevel = defaultValue(options.minimumLevel, 0);
this._maximumLevel = options.maximumLevel;
this._rectangle = defaultValue(options.rectangle, this._tilingScheme.rectangle);
this._readyPromise = when.resolve(true);
// Check the number of tiles at the minimum level. If it's more than four,
// throw an exception, because starting at the higher minimum
// level will cause too many tiles to be downloaded and rendered.
var swTile = this._tilingScheme.positionToTileXY(Rectangle.southwest(this._rectangle), this._minimumLevel);
var neTile = this._tilingScheme.positionToTileXY(Rectangle.northeast(this._rectangle), this._minimumLevel);
var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
if (tileCount > 4) {
throw new DeveloperError('The imagery provider\'s rectangle and minimumLevel indicate that there are ' + tileCount + ' tiles at the minimum level. Imagery providers with more than four tiles at the minimum level are not supported.');
}
this._errorEvent = new Event();
var credit = options.credit;
this._credit = typeof credit === 'string' ? new Credit(credit) : credit;
this._subdomains = options.subdomains;
if (isArray(this._subdomains)) {
this._subdomains = this._subdomains.slice();
} else if (defined(this._subdomains) && this._subdomains.length > 0) {
this._subdomains = this._subdomains.split('');
} else {
this._subdomains = ['a', 'b', 'c'];
}
}
var defaultParameters = freezeObject({
service : 'WMTS',
version : '1.0.0',
request : 'GetTile'
});
function buildImageUrl(imageryProvider, col, row, level) {
var labels = imageryProvider._tileMatrixLabels;
var tileMatrix = defined(labels) ? labels[level] : level.toString();
var subdomains = imageryProvider._subdomains;
var url;
if (imageryProvider._url.indexOf('{') >= 0) {
// resolve tile-URL template
url = imageryProvider._url
.replace('{style}', imageryProvider._style)
.replace('{Style}', imageryProvider._style)
.replace('{TileMatrixSet}', imageryProvider._tileMatrixSetID)
.replace('{TileMatrix}', tileMatrix)
.replace('{TileRow}', row.toString())
.replace('{TileCol}', col.toString())
.replace('{s}', subdomains[(col + row + level) % subdomains.length]);
}
else {
// build KVP request
var uri = new Uri(imageryProvider._url);
var queryOptions = queryToObject(defaultValue(uri.query, ''));
queryOptions = combine(defaultParameters, queryOptions);
queryOptions.tilematrix = tileMatrix;
queryOptions.layer = imageryProvider._layer;
queryOptions.style = imageryProvider._style;
queryOptions.tilerow = row;
queryOptions.tilecol = col;
queryOptions.tilematrixset = imageryProvider._tileMatrixSetID;
queryOptions.format = imageryProvider._format;
uri.query = objectToQuery(queryOptions);
url = uri.toString();
}
var proxy = imageryProvider._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
return url;
}
defineProperties(WebMapTileServiceImageryProvider.prototype, {
/**
* Gets the URL of the service hosting the imagery.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {String}
* @readonly
*/
url : {
get : function() {
return this._url;
}
},
/**
* Gets the proxy used by this provider.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Proxy}
* @readonly
*/
proxy : {
get : function() {
return this._proxy;
}
},
/**
* Gets the width of each tile, in pixels. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileWidth : {
get : function() {
return this._tileWidth;
}
},
/**
* Gets the height of each tile, in pixels. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
tileHeight : {
get : function() {
return this._tileHeight;
}
},
/**
* Gets the maximum level-of-detail that can be requested. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
maximumLevel : {
get : function() {
return this._maximumLevel;
}
},
/**
* Gets the minimum level-of-detail that can be requested. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Number}
* @readonly
*/
minimumLevel : {
get : function() {
return this._minimumLevel;
}
},
/**
* Gets the tiling scheme used by this provider. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {TilingScheme}
* @readonly
*/
tilingScheme : {
get : function() {
return this._tilingScheme;
}
},
/**
* Gets the rectangle, in radians, of the imagery provided by this instance. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Rectangle}
* @readonly
*/
rectangle : {
get : function() {
return this._rectangle;
}
},
/**
* Gets the tile discard policy. If not undefined, the discard policy is responsible
* for filtering out "missing" tiles via its shouldDiscardImage function. If this function
* returns undefined, no tiles are filtered. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {TileDiscardPolicy}
* @readonly
*/
tileDiscardPolicy : {
get : function() {
return this._tileDiscardPolicy;
}
},
/**
* Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it. Event listeners
* are passed an instance of {@link TileProviderError}.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Event}
* @readonly
*/
errorEvent : {
get : function() {
return this._errorEvent;
}
},
/**
* Gets the mime type of images returned by this imagery provider.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {String}
* @readonly
*/
format : {
get : function() {
return this._format;
}
},
/**
* Gets a value indicating whether or not the provider is ready for use.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
ready : {
value: true
},
/**
* Gets a promise that resolves to true when the provider is ready for use.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Promise.}
* @readonly
*/
readyPromise : {
get : function() {
return this._readyPromise;
}
},
/**
* Gets the credit to display when this imagery provider is active. Typically this is used to credit
* the source of the imagery. This function should not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Credit}
* @readonly
*/
credit : {
get : function() {
return this._credit;
}
},
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
* be ignored. If this property is true, any images without an alpha channel will be treated
* as if their alpha is 1.0 everywhere. When this property is false, memory usage
* and texture upload time are reduced.
* @memberof WebMapTileServiceImageryProvider.prototype
* @type {Boolean}
* @readonly
*/
hasAlphaChannel : {
get : function() {
return true;
}
}
});
/**
* Gets the credits to be displayed when a given tile is displayed.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level;
* @returns {Credit[]} The credits to be displayed when the tile is displayed.
*
* @exception {DeveloperError} getTileCredits
must not be called before the imagery provider is ready.
*/
WebMapTileServiceImageryProvider.prototype.getTileCredits = function(x, y, level) {
return undefined;
};
/**
* Requests the image for a given tile. This function should
* not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*
* @exception {DeveloperError} requestImage
must not be called before the imagery provider is ready.
*/
WebMapTileServiceImageryProvider.prototype.requestImage = function(x, y, level) {
var url = buildImageUrl(this, x, y, level);
return ImageryProvider.loadImage(this, url);
};
/**
* Picking features is not currently supported by this imagery provider, so this function simply returns
* undefined.
*
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
* @param {Number} longitude The longitude at which to pick features.
* @param {Number} latitude The latitude at which to pick features.
* @return {Promise.|undefined} A promise for the picked features that will resolve when the asynchronous
* picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
* instances. The array may be empty if no features are found at the given location.
* It may also be undefined if picking is not supported.
*/
WebMapTileServiceImageryProvider.prototype.pickFeatures = function() {
return undefined;
};
return WebMapTileServiceImageryProvider;
});
/*!
* Knockout JavaScript library v3.4.0
* (c) Steven Sanderson - http://knockoutjs.com/
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
(function() {(function(n){var x=this||(0,eval)("this"),u=x.document,M=x.navigator,v=x.jQuery,F=x.JSON;(function(n){"function"===typeof define&&define.amd?define('ThirdParty/knockout-3.4.0',["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in T?a===c:!1}function U(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function V(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function W(a,
c){c&&c!==I?"beforeChange"===c?this.Kb(a):this.Ha(a,c):this.Lb(a)}function X(a,c){null!==c&&c.k&&c.k()}function Y(a,c){var d=this.Hc,e=d[s];e.R||(this.lb&&this.Ma[c]?(d.Pb(c,a,this.Ma[c]),this.Ma[c]=null,--this.lb):e.r[c]||d.Pb(c,a,e.s?{ia:a}:d.uc(a)))}function K(b,c,d,e){a.d[b]={init:function(b,g,k,l,m){var h,r;a.m(function(){var q=a.a.c(g()),p=!d!==!q,A=!r;if(A||c||p!==h)A&&a.va.Aa()&&(r=a.a.ua(a.f.childNodes(b),!0)),p?(A||a.f.da(b,a.a.ua(r)),a.eb(e?e(m,q):m,b)):a.f.xa(b),h=p},null,{i:b});return{controlsDescendantBindings:!0}}};
a.h.ta[b]=!1;a.f.Z[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f a.a.o(c,b[d])&&c.push(b[d]);return c},fb:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;de?d&&b.push(c):d||b.splice(e,1)},ka:f,extend:c,Xa:d,Ya:f?d:c,D:b,Ca:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},ob:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},jc:function(b){b=a.a.V(b);for(var c=(b[0]&&b[0].ownerDocument||u).createElement("div"),d=0,e=b.length;dh?a.setAttribute("selected",b):a.selected=b},$a:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},nd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Mc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=
b;)a=a.parentNode;return!!a},nb:function(b){return a.a.Mc(b,b.ownerDocument.documentElement)},Qb:function(b){return!!a.a.Sb(b,a.a.nb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Wb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Wb(b),c)},$b:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},p:function(b,c,d){var e=a.a.Wb(d);d=h&&m[c];if(a.options.useOnlyNativeEvents||
d||!v)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var l=function(a){e.call(b,a)},f="on"+c;b.attachEvent(f,l);a.a.F.oa(b,function(){b.detachEvent(f,l)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else v(b).bind(c,e)},Da:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==
d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!v||d)if("function"==typeof u.createEvent)if("function"==typeof b.dispatchEvent)d=u.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");else v(b).trigger(c)},c:function(b){return a.H(b)?
b():b},zb:function(b){return a.H(b)?b.t():b},bb:function(b,c,d){var h;c&&("object"===typeof b.classList?(h=b.classList[d?"add":"remove"],a.a.q(c.match(r),function(a){h.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},Za:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.da(b,[b.ownerDocument.createTextNode(d)]):e.data=d;a.a.Rc(b)},rc:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(u.createElement(" "),!1)}catch(c){}},Rc:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Nc:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},hd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},V:function(a){for(var b=[],c=0,d=a.length;c",""],d=[3,""],e=[1,""," "],f={thead:c,tbody:c,tfoot:c,tr:[2,""],td:d,th:d,option:e,optgroup:e},
g=8>=a.a.C;a.a.ma=function(c,d){var e;if(v)if(v.parseHTML)e=v.parseHTML(c,d)||[];else{if((e=v.clean([c],d))&&e[0]){for(var h=e[0];h.parentNode&&11!==h.parentNode.nodeType;)h=h.parentNode;h.parentNode&&h.parentNode.removeChild(h)}}else{(e=d)||(e=u);var h=e.parentWindow||e.defaultView||x,r=a.a.$a(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored"+p[1]+c+p[2]+"
";"function"==typeof h.innerShiv?q.appendChild(h.innerShiv(p)):(g&&e.appendChild(q),
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.V(q.lastChild.childNodes)}return e};a.a.Cb=function(b,c){a.a.ob(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),v)v(b).html(c);else for(var d=a.a.ma(c,b.ownerDocument),e=0;eb){if(5E3<=++c){g=e;a.a.$b(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(h){a.a.$b(h)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=u.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):u&&"onreadystatechange"in u.createElement("script")?function(a){var b=u.createElement("script");b.onreadystatechange=
function(){b.onreadystatechange=null;u.documentElement.removeChild(b);b=null;a()};u.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Wa:function(b){e||a.Y.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&ad[0]?g+d[0]:d[0]),g);for(var g=1===t?g:Math.min(c+(d[1]||0),g),t=c+t-2,G=Math.max(g,t),P=[],n=[],Q=2;cc;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.wc(b);return a.a.Eb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,b);0<=d?this.Ib[d]=c:(this.keys.push(b),this.Ib.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Ib[b]:n}}})();a.b("toJS",a.wc);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.xb):7>=a.a.C?b.getAttributeNode("value")&&
b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ha:function(b,c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.xb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.xb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||
null===c)c=n;for(var e=-1,f=0,g=b.options.length,k;f=p){c.push(r&&k.length?{key:r,value:k.join("")}:{unknown:r||k.join("")});r=p=0;k=[];continue}}else if(58===t){if(!p&&!r&&1===k.length){r=k.pop();continue}}else 47===t&&A&&1=a.a.C&&b.tagName===c))return c};a.g.Ob=function(c,e,f,g){if(1===e.nodeType){var k=a.g.getComponentNameForNode(e);if(k){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');
var l={name:k,params:b(e,f)};c.component=g?function(){return l}:l}}return c};var c=new a.Q;9>a.a.C&&(a.g.register=function(a){return function(b){u.createElement(b);return a.apply(this,arguments)}}(a.g.register),u.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Bc,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(u.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.ua(c);a.f.da(d,b)}
function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,k,l,m){function h(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.V(a.f.childNodes(f));a.a.F.oa(f,h);a.m(function(){var l=a.a.c(g()),k,t;"string"===typeof l?k=l:(k=a.a.c(l.name),t=a.a.c(l.params));if(!k)throw Error("No component name specified");var n=q=++e;a.g.get(k,function(e){if(q===n){h();if(!e)throw Error("Unknown component '"+k+
"'");c(k,e,f);var g=d(e,f,p,t);e=m.createChildContext(g,b,function(a){a.$component=g;a.$componentTemplateNodes=p});r=g;a.eb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.Z.component=!0})();var S={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in S?(c=S[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.rc(b,
g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){var e=b.checked,f=p?g():e;if(!a.va.Sa()&&(!l||e)){var m=a.l.w(c);if(h){var k=r?m.t():m;q!==f?(e&&(a.a.pa(k,f,!0),a.a.pa(k,q,!1)),q=f):a.a.pa(k,f,e);r&&a.Ba(m)&&m(k)}else a.h.Ea(m,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=h?0<=a.a.o(d,g()):k?d:g()===d}var g=a.nc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),k=
"checkbox"==b.type,l="radio"==b.type;if(k||l){var m=c(),h=k&&a.a.c(m)instanceof Array,r=!(h&&m.push&&m.splice),q=h?g():n,p=l||h;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.p(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ea.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.bb(b,c,d)}):(d=a.a.$a(String(d||"")),a.a.bb(b,b.__ko__cssValue,
!1),b.__ko__cssValue=d,a.a.bb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.p(b,g,function(b){var m,h=c()[g];if(h){try{var r=a.a.V(arguments);e=f.$data;r.unshift(e);m=h.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():
b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={ic:function(b){return function(){var c=b(),d=a.a.zb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.W.sb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.W.sb}}},init:function(b,c){return a.d.template.init(b,
a.d.foreach.ic(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.ic(c),d,e,f)}};a.h.ta.foreach=!1;a.f.Z.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(h){g=f.body}e=g===b}f=c();a.h.Ea(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.p(b,"focus",f);a.a.p(b,"focusin",f);a.a.p(b,"blur",g);a.a.p(b,
"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Da,null,[b,d?"focusin":"focusout"]))}};a.h.ea.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ea.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Cb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.createChildContext(c)});var L={};
a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0a.a.C)var g=a.a.e.I(),k=a.a.e.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.e.get(c,k))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.p(d,"selectionchange",l));a.a.e.set(b,k,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.p(b,c,d)}function k(){var c=a.a.c(d());if(null===c||c===n)c="";v!==n&&c===v?a.a.setTimeout(k,4):b.value!==c&&(u=c,b.value=c)}function y(){s||(v=b.value,s=a.a.setTimeout(t,4))}function t(){clearTimeout(s);v=s=n;var c=
b.value;u!==c&&(u=c,a.h.Ea(d(),g,"textInput",c))}var u=b.value,s,v,x=9==a.a.C?y:t;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",t),l("keydown",t)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",t),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",t),l("dragdrop",t),l("drop",t)));l("change",t);a.m(k,null,{i:b})}};a.h.ea.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",
a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Ic;a.a.rc(b,d)}}};a.d.uniqueName.Ic=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,k=null;f&&("string"==typeof f&&(f=[f]),a.a.ra(e,f),e=a.a.Tb(e));var l=function(){k=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ea(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||
"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||(a.a.p(b,"propertychange",function(){g=!0}),a.a.p(b,"focus",function(){g=!1}),a.a.p(b,"blur",function(){g&&l()}));a.a.q(e,function(c){var d=l;a.a.nd(c,"after")&&(d=function(){k=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.p(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==k&&e===k)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ha(b,
e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Da,null,[b,"change"])}else a.j.ha(b,e)};a.m(m,null,{i:b})}else a.Ja(b,{checkedValue:c})},update:function(){}};a.h.ea.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.O=function(){};a.O.prototype.renderTemplateSource=
function(){throw Error("Override renderTemplateSource");};a.O.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.O.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||u;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.qa(b);throw Error("Unknown template type: "+b);};a.O.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,
e);return this.renderTemplateSource(a,c,d,e)};a.O.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.O.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.O);a.Gb=function(){function b(b,c,d,k){b=a.h.yb(b);for(var l=a.h.ta,m=0;m]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Oc:function(b,
c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Gb.dd(b,c)},d)},dd:function(a,f){return a.replace(c,function(a,c,d,e,h){return b(h,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Ec:function(b,c){return a.M.wb(function(d,k){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.Ja(l,b,k)})}}}();a.b("__tr_ambtns",a.Gb.Ec);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.ab="script"===c?1:"textarea"===c?2:"template"==c&&
b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===this.ab?"text":2===this.ab?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Cb(this.n,c):this.n[b]=c};var b=a.a.e.I()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.I();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).jb||(3===this.ab?
b.content:4===this.ab?b:n);a.a.e.set(b,c,{jb:arguments[0]})};a.v.qa=function(a){this.n=a};a.v.qa.prototype=new a.v.n;a.v.qa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Hb===n&&b.jb&&(b.Hb=b.jb.innerHTML);return b.Hb}a.a.e.set(this.n,c,{Hb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.qa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),
d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,k=a.Q.instance,n=k.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.za(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Rb(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.M.yc(b,[d])});a.a.za(c,g)}}function d(a){return a.nodeType?a:0a.a.C?0:b.nodes)?
b.nodes():null)return a.a.V(c.cloneNode(!0).childNodes);b=b.text();return a.a.ma(b,e)};a.W.sb=new a.W;a.Db(a.W.sb);a.b("nativeTemplateEngine",a.W);(function(){a.vb=function(){var a=this.$c=function(){if(!v||!v.tmpl)return 0;try{if(0<=v.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||u;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var k=b.data("precompiled");
k||(k=b.text()||"",k=v.template(null,"{{ko_with $item.koBindingContext}}"+k+"{{/ko_with}}"),b.data("precompiled",k));b=[e.$data];e=v.extend({koBindingContext:e},f.templateOptions);e=v.tmpl(k,b,e);e.appendTo(g.createElement("div"));v.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){u.write("